2009년 12월 27일 일요일

Implementation of Connecting a Socket with Timeout in C#


MFC 로 기본적인 네트워크 프로그래밍만 하다가 C# 네트워크 프로그래밍을 하려니 막히는게 조금 있다...

알다싶이 Socket 에는 Send, Receive 할 때의 Timeout 값을 설정할 수 있다. 하지만 Connect 할 때 Timeout 값을 설정하는 옵션은 없다. 아무런 조치 없이 Connect 할 때 원격지에 연결할 수 없는 상황이 되면 25 ~ 30 초간 Block 상태가 되버린다. google 검색하다가 codeproject 의 article 을 보고 공부할 겸 적어보았다.









Implementation of Connecting a Socket with Timeout in C#



Introduction
여러분도 알다싶이 System.Net.Sockets.TcpClientSystem.Net.Sockets.Socket 클래스는 Socket 에 연결하기 위한 Timeout 값을 가지지 않는다. 하지만 Timeout 값을 설정할 수 있는 방법이 있다. .Net Socket 은 동기/비동기 소켓 연결중에 Connect/BeginConnect 메소드를 호출할 때 Connect Timeout 을 지원하지 않는다. 대신 connect 는 서버 연결에 listening  되지 않거나 어떠한 네트워크 에러로 예외가 전달되기 전에는 매우 긴 시간을 기다리게 한다. 보통 20 ~ 30 초간 기다리게 된다. socket 라이브러리에 SocketOptionName.SendTimeout 이라는 옵션이 있는데 이는 최초의 connect 가 아닌 데이터를 Send 할 때 사용되어지는 옵션이다.



Using the Code

이 기능은 클래스로 구현되어있다. 클래스의 내용은 아래와 같다.



ManualResetEvent 를 이용하는 것이 로직의 핵심이다. ManualResetEvent 는 WaitOne 메소드를 가지고 있는데 오버로드된 WaitOne(TimeSpan, Boolean) 를 이용하였다. MSDN 에 따르면 WaitOne(TimeSpan, Boolean) 메소드는 현재 인스턴스가 signal 신호를 받을때 까지 현재 스레드를 블록시킨다. TimeSpan 을 이용하여 시간 간격을 지정할 수 있으며 기다리기 전에 동기적 도메인에서 빠져나올지를 지정할 수 있다.

메인 스레드에서 timeout 시간까지 또는 스레드 이벤트에 TimeoutObject.Set() 을 이용한 신호를 받을 때 까지  메인 스레드를 블록 시키기 위해 TimeoutObject.WaitOne(timeoutMsec, false) 를 호출한다. WaitOne 메소드는 timeout 값에 의해 블록해제되었을 때 false 를 리턴하며 TimeoutException 을 발생시킨다. 반면에 성공적으로 소켓이 연결되었거나 어떠한 네트워크적 에러로 인해 연결이 실패 되었을 때 true 를 리턴한다.

TcpClient BeginConnect 메소드는 블록되지 않는 비동기 메소드 이다. BeginConnect 메소드를 호출한 후에, WaitOne 을 사용하여 비동기적으로 수행한 콜백 메서드의 결과를 기다리게 된다. 만약 BeginConnect 메소드의 동작이 timeout 시간 안에 완료되지 못한다면, WaitOne 메소드는 신호를 받고, TimeoutException 을 리턴받는다. 만약 BeginConnect 메소드의 동작이 timeout 시간 안에 완료 된다면 CallBackMethod 에서 TimeoutObject.Set() 메소드로 ManualResetEvent 에 신호를 줄것이다. CallBackMethod BeginInvoke 로 delegate 로써 전달시킨 것이다. BeginInvoke 는 동작이 완료되었을 때 Invoke 시키기 위해 CallBackMethod 를 참조한다.



원문 : http://www.codeproject.com/KB/IP/TimeOutSocket.aspx

2009년 12월 20일 일요일

C# BackgroundWorker Tutorial

C# BackgroundWorker Tutorial



Using BackgroundWorker
Visual Studio 2008 에서 designer view 의 toolbox 를 보면 BackgroundWorker 콤포넌트가 있다. (.Net 2.0 부터 지원)




Fist step
toolbox 에서 "BackgroundWorker" 를 선택한다.  그러면 윈도우 하단에 Backgroundworker 객체가 생길것이다.




Second step윈도우 하단 회색 부분에 생성된 Backgroundworker 객체를 선택하면 우측에 Property 창을 볼 수 있다.

Third stepProperty 창에서 번개 모양 아이콘을 선택하자 (번개 모양 아이콘은 Microsoft 에서 이벤트들을 위한 아이콘이다.)
Backgroundworker 는 event-driven(이벤트 구동) 방식으로 동작한다. 즉, 이벤트가 발생되면 해당 이벤트에 해당하는 동작을 수행하게 되어있다. 따라서 우리는 Backgroundworker 를 사용하기 위하여 필수적인 이벤트를 만들어야 한다.

Fourth step4번째 단계는 Backgroundworker 의 이벤트 중에 DoWork 를 더블클릭하여 이벤트 핸들러를 만든다. 이제부터 수동으로 코딩을 해줘야 한다. 즉, DoWork 이벤트를 더블클릭하면 디폴트로 "backgroundWorker1_DoWork" 메소드를 만들게 되고 이 메소드에 실질적인 작업을 구현해주게 된다.

※ 위 UI 작업을 통한 Backgroundworker 생성은 직접 손으로 타이핑하여 구현하는 것 보다 쉽다 (정말?!...)



Adding DoWork
DoWork 의 메소드는 보통의 이벤트 핸들러와 유사하다. C# 코드를 보면 backgroundWorker1_DoWork 메소드를 볼 수 있는데 이 메소드는 DoWork 이벤트를 더블클릭 했을 때 자동으로 생성된 메소드다. 테스트를 위해 Thread.Sleep 커맨들를 넣어 보자. Thread.Sleep 메소드는 Backgroundworker 의 실행을 멈추게 할 것이다. 그러나 전체 CPU를 사용하는 것은 아니다. (cf. C# Sleep Method Pauses Programs - dotnetperls.com)





Properties

다음에 설명은 절대적인 내용은 아니지만 Argument, Result, RunWorkerAsync 메소드를 강조하고 싶다. Backgroundworker 의 property 들은 반드시 어떻게 사용되는지 알 고 있어야 한다. 다음은 코딩할 때 참조할 수 있는 각 property 에 대한 설명이다.

DoWorkEventArgs ee.Argument 와 e.Result 를 포함하고 있다.
즉, DoWorkEventArgs e 는 e.Argument, e.Result 에 접근하는데 사용되어 진다.

e.ArgumentRunWorkerAsync 에 의해서 전달받은 parameter reference 를 얻기 위해서 사용되어 진다.

e.ResultBackgroundworker 처리에 과정을 체크할 수 있다.

backgroundWorker1.RunWorkerAsync (object)worker 스레드에서 작업 시작을 하기위해 호출되어진다.



Executing code
Backgroundworker 에 argument 와 return value 로  명령들(정보들)을 전달해 줄 수 있다. 다음의 테스트 코드는 argument 를 전달해 주는 방법과 Backgroundworker 를 invoke 시키는 방법, 스레드의 결과를 전달받는 방법을 보여주고 있다.




예제 argument 의 생성위 코드에서 TestObject  는 예제 객체이다. 아마도 여러분의 프로그램에서는 더 중요하고 복잡한 정보가 담겨질 것이다. 위에선 C# collection initializer 문법이 사용되었다.

어디서든 호출될 수 있다.
여러분의 코드 어디에서든 RunWorkerAsync 를 호출할 수 있다. 예제에선 생성자에서 호출하고 있지만 다른 어디에서든 호출할 수 있다.



Implementation DoWork

여러분이 원하는 로직으로 DoWork 의 메소드를 작성할 수 있다. DoWork 의 메소드(이벤트 핸들러) 에서 구현부와 리턴값으로 DoWorkerEvnetArgs 를 사용하라. 아래의 코드에서는 argument 와 result 를 RunWorkerAsync 호출로 연결시키고 있다. 기억하라!! TestObject 는 RunWorkerAsync 에 파라미터로 전달받은 것이고, e.Argument 로 받게 된다. 또한 반드시 캐스팅 연산을 해주어야 한다.



※ e.Argument 는 항상 RunWorkerAsync 에 의해 background worker 에 전달한 것을 포함하고 있다. 간단히 캐스팅을 통해 원래의 타입으로 변환하여 사용할 수 있다.
 또한 DoWork 의 이벤트 핸들러 메소드 안에서 처리한 결과를 e.Result 에 담아 리턴 시키도록 하자!!



Using RunWorkerCompleted
앞서 생성한 backgroundworker1 아이콘을 클릭한 후 우측의 번개 모양 아이콘을 선택하면 이벤트 목록이 나온다. 거기서 RunWorkerCompleted 이벤트를 사용할 수 있다. RunWorkerCompleted 를 더블클릭하자.
backgroundWorker1_RunWorkerCompleted 라고 디폴트로 이벤트 핸들러가 생성된다. 이 메소드에 argument 를 받는 코드를 넣어준다.





Tips

Backgroundworker 는 Windowns Form 안에서 스레드들을 구조적으로 overlay 시킨 것으로 매우 직관적이다.
사용하는 단계를 다시 한번 언급한다면,

First, call RunWorkerAsync with an argument.Backgroundworker 안의 메소드에 어떠한 argument 라도(null 포함) 전달할 수 있다. 객체 상속 관계에 속한 객체도 전달 가능하다.(다형성)

Second, custom processing is run.DoWork 의 메소드에서 여러분의 코드를 실행시킬 수 있다.

Third, it finished.여러분이 작성한 처리가 완료되면, RunWorkerCompleted 가 호출된다. 이 메소드 안에서 스레드에서 처리한 (DoWork 의 메소드 에서 처리한) 결과를 전달받을 수 있다. 이런 방법으로 여러분이 작성한 Backgroundworker 객체는 다른 스레드에 있는 객체를 수정한다.



Summary
위에서 C# 언어를 통해 Backgroundworker 컨트롤(컴포넌트) 를 어떻게 사용하는지 살펴보았다. 일반적으로 여러분은 많은 스레들 사용해야 할 때, ThreadPool 사용을 선호한다. 스레드들이 항상 유용한 것은 아니다.(I/O operation 등등). 멀티 코어 시스템에서 우리는 스레드들의 사용이 필요로 하고, Backgroundworker 의 사용은 훌륭한 선택이 될 것이다.









Backgroundworker 클래스 멤버를 살펴보자











메소드와 프로퍼티, 이벤트들을 연관있는 것끼리 묶어 봤다.

RunWorkerAsync - DoWork
RunWorkerAsync 메소드는 백그라운드 작업을 실행 시키는 메소드다. 즉, 스레드를 시작시킨다.
RunWorkerAsync 메소드가 호출되면 DoWork 이벤트가 발생된다. 따라서 DoWork 이벤트의 이벤트 핸들러에 백그라운드에서 처리할 작업을 정의해주면 된다.


WorkerReportsProgress - ReportProgress - ProgressChanged
1. WorkerReportsProgress 프로퍼티
Backgroundworker 객체가 처리작업에 대한 보고(report)를 할 수 있는지에 대해 설정하거나 얻어온다.
default 값은 false 이다. 이 값을 true 로 설정해야 ProgressChanged 이벤트를 발생시킬 수 있다.

2. ReportProgress 메소드
ProgressChanged 이벤트를 발생 시킨다. 매개변수로 Backgroundworker 객체의 작업 완료 정도를 0 에서 100 까지 값을 갖는 퍼센테이지 값을 전달한다. 또한 object 타입으로 RunWorkerAsync 로 전달한 상태 객체값을 전달한다.

3. ProgressChanged 이벤트
ReportProgress 메소드가 호출되었을 때 발생하는 이벤트 이다.


WorkerSupportsCancellation - RunWorkerCompleted1. WorkerSupportsCancellation 프로퍼티
Backgroundworker 객체가 스레드 취소 기능을 설정하거나 설정값을 얻는다. default 값은 false 이다. 이 값을 true 로 설정해야 RunWorkerCompleted 이벤트가 발생된다.

2. RunWorkerCompleted 이벤트
스레드의 완료, 취소, 예외가 발생했을 때 발생하는 이벤트 이다. 이 이벤트의 매개변수인 RunWorkerCompletedEventArgs 객체를 통해 작업이 중되 취소되었는지, 예외가 발생했는지 알 수 있다.


CancellationPending
Backgroundworker 스레드를 소유한 응용프로그램에서 스레드에 작업 취소를 요청했는지에 대한 설정값을 얻는다.



ProgressChanged


ReportProgress 메소드의 인자로 전달한 값을 ProgressChangedEventArgs 객체를 통해 사용할 수 있다.



RunWorkerCompleted

RunWorkerCompletedEventArgs 객체를 통해 이벤트 정보를 얻을 수 있다.
Cancelled : 스레드의 취소 요청에 의해 스레드가 취소 되었는지에 대한 bool 값
Error        : 스레드의 예외 발생에 의한 스레드가 종료 되었는지에 대한 Exception 타입의 값
Result      : 스레드가 정상종료 되었을 때 object 값










BackgroundWorker 스레드의 실행

1. 다른 스레드 즉, BackgroundWorker 스레드에서 실행할 이벤트 처리기를 생성한다. 아래 코드에서는 threadWaitPermit_DoWork 이벤트 핸들러를 생성하였다.


BackgroundWorker threadWaitPermit = new BackgroundWorker();
threadWaitPermit.DoWork += new System.ComponentModel.DoWorkEventHandler(threadWaitPermit_DoWork);


2. 메인 스레드에서 BackgroundWorker 스레드를 실행시키기 위해서 RunWorkerAsync 메소드를 호출한다. RunWorkerAsync 메소드를 호출하면 위에서 DoWork 이벤트에 등록한 이벤트 핸들러가 실행된다.


threadWaitPermit.RunWorkerAsync(_evtArgs);


RunWorkerAsync 메소드 호출 시 전달한 매개변수는 DoWork 이벤트 핸들러의 메소드에서 적절한 형변환을 거쳐 사용할 수 있다. 매개변수 DoWorkerEventArgs 의 Argument 매개변수가 전달받은 매개변수 이다. 또한 결과 값을 Result 프로퍼티에 넣어준다. 보통 Argument 매개변수값을 넣어주면 좋다.


private void threadWaitPermit_DoWork(object sender, DoWorkEventArgs e)
{
    CustomEventArgs args = e.Argument as CustomEventArgs;
    ...
    e.Result = args;
}




BackgroundWorker 스레드의 취소

BackgroundWorker 스레드를 생성한 스레드에서 BackgroundWorker 스레드의 동작을 취소 시킬 수 있다.

1. BackgroundWorker 의 WorkerSupportsCancellation 프로퍼티값을 true 로 설정한다.


threadWaitPermit.WorkerSupportsCancellation = true;


2. BackgroundWorker 의 스레드를 취소 하기 위해 CancelAsync 메소드를 호출한다. 위에서 WorkerSupportsCancellation 프로퍼티값을 true 로 설정하지 않은 채 CancelAsync 메소드를 호출하면 예외가 발생하므로 WorkerSupportsCancellation 프로퍼티값을 검사하는 방어적 코딩을 해준다.


if (threadWaitPermit.WorkerSupportsCancellation)
    threadWaitPermit.CancelAsync();


3. BackgroundWorker 스레드 작업을 수행하는 DoWork 이벤트의 이벤트 핸들러에서 CancellationPending 프로퍼티값을 검사한다. CancelAsync() 메소드를 호출하면 CancellationPending 프로퍼티 값이 true 가 된다.


private void threadWaitPermit_DoWork(object sender, DoWorkEventArgs e)
{
    ...
    if (threadWaitPermit.CancellationPending)
        ....




BackgroundWorker 스레드의 작업과정 갱신

보통 스레드의 작업 처리 과정을 메인 스레드(UI스레드)의 프로그레스바를 그려주는 것으로 표시한다. BackgroundWorker 스레드에서 이러한 일반적인 처리 과정을 돕는다.

1. WorkerReportsProgress 프로퍼티값을 true 로 설정한다. 스레드에서 작업 갱신 이벤트(ProgressChanged) 발생시 호출될 이벤트 핸들러를 등록한다.


threadWaitPermit.WorkerReportsProgress = true;
threadWaitPermit.ProgressChanged += ProgressChangedEventHandler(threadWaitPermit_ProgressChanged);


2. BackgroundWorker 스레드에서 UI 작업을 위해 ReportProgress() 메소드를 호출한다. ProgressChanged 이벤트에 등록된 이벤트 핸들러가 호출된다. ReportProgress 메소드의 매개변수로 사용할 정보값을 넘겨준다.


private void threadWaitPermit_DoWork(object sender, DoWorkEventArgs e)
{
    ...
    BackgroundWorker worker = sender as BackgoundWorker;
    worker.ReportProgress(10);
    ...


3. ProgressChanged 이벤트에 등록한 이벤트 핸들러를 구현해준다. 이 이벤트 핸들러에서만 UI 스레드에 접근하여 값을(보통 컨트롤) 변경시켜줄 수 있다. ProgressChangedEventArgs 매개변수에서 ProgressPercentage 프로퍼티값이 ReportProgress 메소드에서 전달한 매개변수값이다.


private void threadWaitPermit_DoWork(object sender, ProgressChangedEventArgs e)
{
    ...
    progress1.Value = e.ProgressPercentage;
    ...



※ ReportProgress 메소드는 2개의 메소드로 오버로딩되어 있다.

두번째 매개변수의 타입이 Object 타입이므로 사용자가 정의한 특정 정보를 담은 데이터를 넘겨줄 수 있다.

ProgressChangedEventArgs 타입의 멤버를 보면 UserState 프로퍼티를 이용하여 두번째 매개변수로 넘긴 값을 받을 수 있다.




BackgroundWorker 스레드의 종료

BackgroundWorker 스레드 종료 이벤트는 RunWorkerCompleted 이다. 이 이벤트에 이벤트 핸들러를 연결 시키면 스레드 종료 시 연결시킨 이벤트 핸들러가 호출된다. 아래 코드에서는 threadWaitPermit_RunWorkerCompleted 이벤트 핸들러를 연결 시켰다.


threadWaitPermit.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(threadWaitPermit_RunWorkerCompleted);


BackgroundWorker 스레드에서 스레드가 종료되는 상황(취소, 완료, 예외)에 따른 처리를 RunWorkerCompleted 이벤트 핸들러에서 처리해주면 된다.


private void threadWaitPermit_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // 스레드 작업에서 예외가 발생하여 종료된 경우
    if (e.Error != null)
        ...

    // 스레드 작업에서 취소 명령이 발생한 경우
    // 스레드에서 e.Cancelled 프로퍼티에 값을 설정해 줄 수 있다.
    if (e.Cancelled)
        ...


2009년 12월 4일 금요일

C# ThreadPool Usage

C# ThreadPool Usage




원문 : C# ThreadPool Usage


C# 프로그래밍 언어에서는 thread pool 들을 이용하여 병렬로 작업을 처리한다. ThreadPool 이라는 built-in framework 을 사용하여 (BCL) 작업과 동시에 ProgressBar 를 업데이트 시킬 수 있다.





Understanding ThreadPools
.Net framework 에서는 System.Threading 네임스페이스로 ThreadPool 클래스를 지원한다. 이 클래스는 객체 생성없이 바로 접근할 수 있는 static class 이다. 이 클래스 에서는 thread pool 의 필수적인 부분을 지원해 준다. 또한 이 클래스는 thread pool 패턴을 구현한 클래스 이다. 이는 백그라운드에서 동작하는 많은 분할된 작업을 실행하는데 도움을 준다.


Maximum number of threads

보통 스레드의 최대 개수를 아는 것은 중요하지 않다. .Net ThreadPool 의 전반적인 관점은 ThreadPool 안에서 스레드들의 내부적인 관리이다. Multicore machines 들은 오래된 machines 보다 많은 스레드를 가진다.
cf) ThreadPool에서 사용 가능한 Thread의 개수는?
Microsotf 에서 설명하기로는 thread pools 는 전형적으로 스레드의 최대 개수를 가진다. 만약 모든 스레드가 사용중인 상태일 경우, 추가적인 작업들은  이용 가능한 스레드를 할당 받기 전까지 queue 에 놓여진다.


Usage locations (ThreadPool 의 적절한 사용 위치)

서버와 일괄 처리 응용프로그램이 있다. ThreadPool 은 더 적은 비용으로 스레드를 생성하는 로직을 가진다. 스레드들이 이미 만들어져 있고, 단지 필요할 때 연결만 시켜주면 되기 때문이다.  왜 ThreadPool-Style 코드가 서버에 사용되어지는 이유는 다음과 같다.
MSDN 에 따르면, "서버 응용프로그램에서는 종종 ThreadPool 이 사용되어 진다. 각각의 입력 요청들은 thread pool 의 스레드로 할당되고, 요청들은 비동기적으로 처리된다. primary thread 의 부담을 주지 않고, 순차적인 요청들의 처리를 지연시키지 않으면서." [How to Use a Thread Pool - MSDN]


ThreadPool vs. BackgroundWorker
여러분이 만약 Windows Forms 를 사용한다면, 더 간단한 스레딩 요구를 위해 BackgroundWorker 콤포넌트를 사용하기를 추천한다. BackgroundWorker 는 많은 장점이 있다. 많은 프로세서의 일괄 처리를 위해서는 ThreadPool 이 필요하다. [C# BackgroundWorker Tutorial - dotnetperls.com]

Thread Consideration
   - 요구 : 응용프로그램이 일괄 처리(Batch Processing)를 한다.   - 고려 : ThreadPool

   - 요구 : 응용프로그램이 3개 이상의 스레드를 만든다.   - 고려 : ThreadPool

   - 요구 : 응용프로그램이 Windows Form 을 사용한다.   - 고려 : BackgroundWorker

또한 여러분이 스레드를 어떻게 사용하는지 특성에 따라 최적의 코드를 찾을 수 있다. 다음의 자료는 스레드 시나리오에 의한 비교이다.

   - 요구 : 한개의 여분의 스레드를 필요로 한다.   - 사용 : BackgroundWorker

   - 요구 : 짧은 시간 사용하는 많은 스레드를 필요로 한다.
   - 사용 : ThreadPool


Requirements

threading 은 중요하다. 하지만 긴 실행시간을 갖지 않고, 한번에 한가지 동작을 하는 대부분의 응용프로그램에서는 중요히지는 않다. 또한 인터페이스 이용이 많은 응용프로그램에서는 중요하지 않으므로, 스레드의 사용을 피하라.


Hook up methods (작업과 스레드를 연결하는 메소드)

QueueUserWorkItem 메소드를 사용한다. 스레드에 의해서 처리할 메소드를 가지고 있을 때, 반드시 QueueUserWorkItem 을 이용하여 스레드와 메소드를 연결시켜줘야 한다. 어떻게?? WaitCallBack 를 사용해야 한다. MSDN 에 WaitCallBack 는 ThreadPool 을 실행하였을 때 호출되어지는 delegate callback 메소드라고 설명하고 있다. 즉, callback delegate 이다.


Use WaitCallback
void Example()
{
    // Hook up the ProcessFile method to the ThreadPool.
    // Note: 'a' is an argument name. Read more on arguments.
    ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessFile), a);
}

private void ProcessFile(object a)
{
    // I was hooked up to the ThreadPool by WaitCallback.}


실행하기 위한 메소드를 ThreadPool 의 대기 큐에 넣는다. thread pool 의 스레드를 이용 가능할 때 해당 메소드가 실행된다.


보이는것과 같이 첫번째 매개변수로 WaitCallBack 받는다.



 WaitCallBack 는 스레드 풀의 스레드에 의해서 실행되어질 callback method 를 나타낸다. 타입에서 알 수 있듯이 delegate 이다.



WaitCallBack 은 void 리턴형과, object 인자를 받는 메소드를 가리킨다.



Use parameters
사용자가 정의한 특별한 클래스를 파라미터로 전달할 수 있다. 특별한 클래스 안에는 사용자의 특별한 값들이 들어있다. 파라미터에는 object 타입을 전달할 수 있으므로 특별한 클래스 객체를 사용할때는 캐스팅 해주어야 한다.



What's going on
위 코드에서 스레드에서 처리할 ProcessFile 메소드로 특별한 클래스 객체를 통하여 2개의 값을 전달하였다. 즉, object 타입 파라미터 이므로 모든 형식을 전달할 수 있다.



Use ProgressBar
스레드풀의 스레드에서 Windows form 안에 사용할 수 있는 ProgressBar 컨트롤에 접근할 수 있다. ProgressBar 의 초기 설정값을 다음과 같이 설정한다.



ProgressBar position.ProgressBar 의 색깔이 있는 부분의 길이는 Maximum 값의 퍼센테이지 이다. 그래서 만약 Maximum 값이 6 이라면 3의 값일 때 ProgressBar 의 색깔 부분은 반이 채워질 것이다.



Call Invoke on ProgressBar
이번 내용에서는 ProgressBar 인스턴스에 어떻게 Invoke 메소드를 사용하는지에 대해서 알아본다. 불행하게도 worker thread 에서는 Windows Control 에 접근할 수 없다. (windows 의 UI 요소 즉, 컨트롤에 접근할 수 있는 것은 UI 스레드 뿐이며, UI 스레드는 메인 스레드 뿐이다.) worker thread 는 UI 스레드가 아니다. 따라서 ProgressBar 접근을 위해 delegate 와 Invoke 를 사용해야 한다.



Delegate syntax.위 코드의 시작 부분에 보면 UpdateBar 메소드로 선언된 delegate 를 볼 수 있다. 이상해 보이지만 이 delegate 문법을 사용해야 한다.


More work needed.위 프로그램은 여러분이 어떻게 ProgressBar 의 Maximum, Minimum 을 설정할 수 있는지 보여주고 있다. 그리고 작업이 끝난 후에 ProgressBar 의 크기를 증가시키기 우해 어떻게 delegate 메소드를 Invoke 시키는지 보여주고 있다.



Threads in debugger
이번 내용은 Visual Studio 2008 debugger 에서 스레드를 볼 수 있는 방법을 설명한다. 여러분의 프로그램이 동작중일 때 스레드를 눈에 볼 수 있게 다음 단계를 거친다.
첫 번째, 응용프로그램을 debug 모드로 연다.
두 번째, 툴바의 초록색 화살표를 클릭하여 디버거를 수행 시킨 다음에, 스레드들이 동작하면 pause 버튼을 누른다.








스레드 창 메뉴의 위치는 다음과 같다.
(디버그 중에 아래와 같은 메뉴가 보인다.)




Constraining worker threads

여러분이 만약 dual-core 또는 quad-core 시스템을 갖고 있다면, 2개 또는 4개의 스레드를 원할것이다. _threadCount 필드를 유지하고, 동작중인 스레드의 개수를 추적하여 스레드 개수를 조절할 수 있다. thread count 필드 값을 사용할 때 필드값이 잘못 읽혀지거나 쓰여지는 것을 피하기 위해 C# 언어에 있는 lock 을 사용해야만 한다. Locks 은 여러분의 스레드가 다른 스레드로 부터 변경 되는 것을 방지해준다.



What we see.위 코드는 비동기적으로 실행되는 메소드이다. worker thread 의 개수가 4개 보다 작아질때까지 동작하지 않는다. 위 코드는 quad-core machine 에 좋다. lock statement 에 대해서는 다음을 참조 [C# Lock Statement - dontnetperls.com]



Controlling thread counts
threadPool 의 SetMinThreads 메소드를 사용하여 처리량과 성능을 향상시킬 수 있다. 이를 위한 가장 이상적인 최소의 스레드 개수에 대한 참고 자료가 있다. 다음을 참조 [C# ThreadPool.SetMinThreads Method - dontnetperls.com]



Summary

여기서 우리는 C# 프로그램에 많은 스레드들을 효율적으로 관리할 수 있는 threadPool 클래스를 사용할 수 있는 방법을 알아보았다. Windows Form 응용프로그램에서 스레드를 이용한 ProgressBar 와 빠른 UI 변경은 인상적이고 어렵지 않다. 하지만 스레드는 복잡하고 많은 버그를 일으킬 소지가 있다. threadPool 은 매우 간단하다. 하지만 여전히 스레드는 어렵다.














동기화 이벤트
스레드를 활성화 하거나 일시 중단하는 데 사용할 수 있고 신호를 받은 상태 및 신호를 받지 않은 상태 중 한 가지 상태가 지정되는 개체이다. 동기화 이벤트에는 두가지 종류가 있다.

- AutoResetEvent
- ManualResetEvent

둘 사이의 유일한 차이점은 AutoResetEvent 의 경우 스레드를 활성화 할때마다 신호를 받은 상태에서 신호를 받지 않은 상태로 자동으로 변경된다. ManualResetEvent 를 사용하면 신호를 받은 상태를 통해 스레드를 그 수에 상관없이 활성화할 수 있고, 해당 Reset 메소드를 호출한 경우에만 신호를 받지 않은 상태로 되돌릴 수 있다.



WaitHandle


WaitHandle은 공유 자원에 대한 배타적 접근 허용을 위해서 제공되는 클래스다. 다시 말해서, WaitHandle은 커널 객체에 대한 동기화를 제공하는 Win32 API에 대한 래퍼 클래스다. (말이 슬슬 어려워 진다.... ㅎㅎㅎ)
※ 배타적 접근
하나의 스레드가 공유 자원을 사용하고 있으면, 다른 스레드들이 접근하지 못하도록 하는 것을 배타적 접근 이라고 한다.











■ 참조
1. ThreadPool 클래스 - MSDN
2. 방법: 스레드 풀 사용(C# 프로그래밍 가이드) - MSDN
3. C# BackgroundWorker Tutorial - dotnetperls.com
4. C# 쓰레드 이야기 - 12. 식사하는 철학자 - hanb.co.kr
5. [C# Thread]#9. 임계영역

2009년 11월 20일 금요일

TabIndex 꼬일 때 설정하는 법

TabIndex 꼬일 때 설정하는 법




 이 방법을 알기 전에는 말 그대로 TabIndex 가 꼬여서 정신을 못차리더라. 아무리 Property 의 TabIndex 를 순서대로 먹여도 지맘대로 왔다갔다 한다. 하지만.. 알고보니 꼬인게 아니라 내가 막 설정한 것이였다. ㅠㅠ

 TabIndex 설정값에 따라 컨트롤들의 이동이 무조권 TabIndex 값 순서에 따라 이동하는게 아니다. MSDN 에 있는 TabIndex 설명을 빌리자면,

" TabIndex 는 0 보다 크거나 같은 모든 유효 정수로 구성될 수 있으며, 숫자가 낮을수록 TabIndex 에서 앞에 온다.
  같은 부모 컨트롤에 있는 여러개의 컨트롤이 같은 TabIndex 를 가질 경우 해당 컨트롤의 Z 순서가
 컨트롤의 순환을 결정한다.
"


■ 방법.
아마 많은 컨트롤을 담은 폼에서 문제가 발생할 것이다. 여기선 간단한 것으로 테스트로 간단하게.
일반적으로는 속성창을 이용해서 TabIndex 를 설정한다.


하지만 이것 만으로는 한계가 있다는 것...




1. Visual Studio 2008 에서 "보기 → 탭 순서" 를 선택한다.
cfile29.uf.1766F83550AEC2D81E19A5.bmp


아래와 같이 설정된 TabIndex 값이 컨트롤 위로 출력 된다.





2. 원하는 순서대로 컨트롤을 클릭하여 TabIndex 를 변경할 수 있다.


위와 같이 선택한 대로 순서가 결정된다.


2009년 11월 13일 금요일

Text 파일 읽기

Text 파일 읽기




 WinForm 에서 텍스트 파일을 읽어와서 TextBox 에 출력시켜 주는 기능을 구현했다. 물론 많이 편해졌지만 새로운 사실 몇가지를 알게되어 정리해 본다.


■ 파일 읽기

 파일을 읽기위해 FCL 에서 제공해 주는 기능은 많다. 파일을 Byte 배열로 읽을 수 도 있고 string(문자열) 으로도 읽을 수 있다. 






1. FileStream 클래스를 이용하여 파일을 연다.
FileStream 클래스는 파일에 대해 Stream 을 제공하여 동기 및 비동기 읽기/쓰기 작업을 할 수 있게 도와준다.
cf) MSDN : FileStream 클래스

2. StreamReader
StreamReader 클래스를 이용하여 Stream 을 쉽게 조작할 수 있게 한다. 여기서 주의해야 할 것은 StreamReader 생성자의 Encoding 매개변수 이다.

처음 이 매개변수 값을 System.Text.Encoding.UTF8 로 하였다. 그 결과 영문자를 읽는데는 문제가 없었으나 한글은 깨져서 보인다.



UTF8 이라...  예전 유니코드 공부하면서 나온 단어인데..

UTF8 은 유니코드가 아닌 문자 집합을 유니코드로 인코딩 하는 하나의 방법이다. 이런 방법으로 UTF16, UTF32 가 있다.
UTF8 은 인코딩 방법은 문자에 따라 1, 2, 3, 4Byte 로 인코딩을 수행한다.

그렇다면 StreamReader 생성자에서 Encoding 매개변수가 의미하는 것은 ??
읽을 Stream 이 어떤 인토딩 방법으로 인코딩 되어 있는지 정보를 넘겨주는 것이다.
그래야 Stream 을  우리가 원하는 문자열로 읽을 때 해당 인코딩 방법을 사용하여 우리에게 알려줄게 아닌가.

여기서 또 한가지!! UTF8 로 했을 때 왜 한글이 깨질까??
이건 우리가 읽을 파일에 따라 다르다. 해당 파일이 UTF8로 인코딩 되었다면 UTF8로 읽어도 한글이 제대로 보여질 것이다. 하지만 지금 내 환경은 Windows Vista 에서 작성한 txt 파일을 읽는 것이다. Windows Vista 는 UTF16 을 사용한다. 그러니 UTF8 로 지정해 주니 제대로 읽지 못하는 것.
위와 같이 Default 로 해주면 시스템에 지정된 인코딩 방법을 넘겨주게 된다.



■ 개행 - "\r\n"

 아마 파일의 문자열을 읽어와 TextBox 에 출력시키면 개행이 하나도 적용되어 있지 않을 것이다. TextBox 에서 Text 프로퍼티 말고 AppentText 를 이용해 주었다. "\n" 을 넣어주어도 이상하게 출력이 된다. 정말 기본적인 지식으로 넘어가면,
Windows 는 개행문자를 두개 사용한한다. 바로 "\r\n"





이제 제대로 읽어졌다.





■ 참조

1. About Unicode
2. 멀티바이트 - 유니코드 서로변환 함수 표 [스크랩]
3. 유니코드에 대비한 프로그램을 작성하는 여섯가지 원칙 [스크랩]

2009년 11월 5일 목요일

Registry 다루기

Registry 다루기



 보통 지금까지 많은 윈도우즈 응용프로그램들이 자신의 정보를 저장하기 위하여 ini 파일 및 레지스트리를 사용하였다. 레지스트리는 응용프로그램, 사용자 및 기본 시스템 설정 정보에 대한 저장소 역할을 수행한다.
 .Net Framework 플랫폼에서의 응용프로그램은 어셈블리에 그 정보를 저장하기도 하고, WPF 에서는 응용프로그램 설정 이라고 하여 따로 파일을 하나 사용한다. 개인 적으로 레지스트리에 정보를 저장하는 방법을 별로 좋아하지는 않지만 알아 두어야 할 것 같다.



■ Namespace

using Microsoft.Win32;

Microsoft.Win32 네임스페이스를 사용한다. 이 네임스페이스 안에는 두가지 유형의 클래스들이 정의되어 있다.
- 운영체제(OS) 에서 발생시킨 이벤트를 처리하는 클래스
- 시스템 레지스트리를 관리하는 클래스

cf) MSDN : Microsoft.Win32 네임스페이스



■ Registry, RegistryKey  Class
Registry

Windows 기반 컴퓨터의 레지스트리에 있는 표준 루트 키 집합을 제공한다.

즉, 요 놈들을 말한다.

MSDN 을 보니 PerformanceData, DynData 루트 키도 있다고 하는데 난 보질 못했다...

사용 방법은 다음과 같다.

이미지 출처 : MSDN

Registry.Users 값을 할당해 줌 으로써 rk 는 HKEY_USERS 하위의 레지스트리 키에 접근/조작 가능하다.


RegistryKey

Windows Registry 의 키 수준 노드를 나타 낸다고 MSDN에 써 있는데... 첨엔 몬소린가 했다. ㅋㅋㅋ
즉, Registry 클래스를 통하여 레지스트리의 트리 구조에 접근하고, RegistryKey 클래스 인스턴스를 통하여 레지스트리 키 값을 추가/삭제/수정 할 수 있다는 것이다. "키 수준의 노드" 란 말이 레지스트리 트리에서 레지스트리 키를 지칭하는 것 같다.

사용 방법은 다음과 같다.


Registry.SetValue() 메소드를 통해서 키 값을 설정하고 있다.
여기서 알 수 있는 것은 레지스트리 경로를 궂이 Registry 클래스를 통해서 얻지 않아도 된다.



■ 사용 예











또한 레지스트리의 HKLM 의 경우 관리자 권한의 응용프로그램만이 쓰기 권한이 가능하다.
따라서 Windows Vista 에서는 UAC 에 의해 접근 실패할 것이다. 응용프로그램의 메니페스트를 수정하여 응용프로그램이 항상 관리자 권한으로 수행될 수 있게 권한을 상승시키자.
참조) Windows Vista 이상 OS 에서 응용프로그램 권한 상승

Form Border Style - None

Form Border Style - None




 오왕 오왕 오왕~

기본적인 Winform 윈도우 화면은 이렇다.





1. Group Box 를 추가하여 기존 컨트롤 들을 Group Box 에 넣는다.





2. Form 의 몇가지 설정을 해주자

- Form 의 시작 위치 설정



- Border Style 을 None으로 설정




3. Group Box 의 몇가지 설정을 해주자

- Dock 은 Fill 로 설정



- Font 는 1pt 로 설정




4. 실행 결과




오왕 오왕 오왕~ ㅎㅎ

2009년 11월 3일 화요일

Ping Test

Ping Test



.Net 에서 Ping Test 하는 MSDN 의 예제이다.

Ping, PingOptions, PingReply 클래스를 이용하며, 이 들의 네임스페이스는 System.Net.NetworkInformation 이다.

using System.Net.NetworkInformation;


2009년 10월 30일 금요일

Shallow Copy / Deep Copy

Shallow Copy / Deep Copy




 .Net 에서는 2가지 데이터 타입이 존재한다.

- Value Teyp- Reference Type


아마도 BCL 을 사용하던, 사용자 정의 클래스를 사용하던 Reference Type 을 사용하는 경우가 대부분 일 텐데, 참조 변수간의 값 할당은 Shallow copy 에 의해서 참조 값이 복사되게 된다. 즉, Managed Heap 에 할당된 하나의 객체를 여러 참조 변수들이 가리키게 된다.


이미지 출처 : C# Object Clone Wars


메모리의 효율적 사용 면에서는 Shallow Copy 에 의한 Clone 을 갖는게 좋을 수 있다. 하지만 경우에 따라서는 독립적인 복사본을 만들어야 되는 경우가 있다.(스레드 사용하면서..)



■ 참조값의 할당 (=)



위와 같이 Person 타입 객체를 참조하는 참조 변수 yuk 가 있을 때 youngho 라는 참조 객체에 yuk 값을 할당 연산자 = 로 값을 대입하면 Shallow Copy 일까, Deep Copy 일까??  난 Shallow Copy 일 거라 추측했지만 Shallow Copy 가 아니다. 단지 yuk 가 참조하던 객체를 youngho 변수도 참조하게 될 뿐이다.



■ Shallow Copy - MemberwiseClone()
Shallow Copy(얕은 복사) 가 참조 타입에 대하여 막연히 객체에 대한 참조를 복사 받는다고 알고 있었는데 Value Type 에 대해 어떤 동작을 하는지는 미처 생각하지 못했다.

1. .Net 에서 Shallow Copy
- 값 타입(Value Type)은 동일한  복사본을 생성
- 참조 타입(Reference Type)은 객체를 가리키는 참조를 리턴 받는다.



즉, Shallow Copy 라고 하더라도, Value Type 에 대해서는 독립적인 메모리를 할당 한다.

.Net 에서 Shallow Copy 를 지원하기 위해 MemberwiseClone() 메소드를 지원한다.



System.Object 의 멤버 이므로 .Net 의 모든 클래스(FCL)들이  MemberwiseClone() 메소드를 갖는다.
protected 접근 제한자를 갖기 때문에 클래스 외부에서 바로 사용할 수는 없고, 클래스 멤버 메소드에서 호출할 수 있다.
사용 예는 다음과 같다.


이미지 출처 :  데브피아 : 객체 복사(깊은 복사/얕은 복사)




■ Deep Copy - ICloneable, Clone()

1. .Net 에서 Deep Copy
- 값 타입, 참조 타입에 관계없이 새로운 복사본을 복사한다.




.Net 에서는 Deep Copy 를 지원하기 위해 ICloneable 인터페이스를 이용한 Close() 메소드를 지원한다.







ICloneable 인터페이스는 System 네임스페이스안에 정의 되어 있으며 Clone() 이라는 하나의 메소드를 갖는다.
요약 설명대로 하나의 인스턴스에 대해서 동일한 새로운 인스턴스를 생성하여 리턴한다. 이때 리턴 타입은 object 타입이다.

MSDN 문서에 따르면 ICloneable 의 Clone() 메소드가 Deep Copy 를 수행한다고 명시적으로 연급되어 있지는 않는다.
cf) C# Object Clone Wars

사용 예는 다음과 같다.


이미지 출처 : 데브피아 : 객체 복사(깊은 복사/얕은 복사)



■ History

1. 2009.10.31 - posting


■ 참조
1. C# Object Clone Wars
2. 데브피아 : 객체 복사(깊은 복사/얕은 복사)