Vb.net 쓰레드가 끝날때 까지 기다리는 방법

스레드

https://msdn.microsoft.com/ko-kr/library/ms173178.aspx

1. 스레딩을 사용하면 Visual Basic 또는 C# 프로그램에서 동시 처리(concurrent processing)를 수행하여 한 번에 여러 작업을 진행할 수 있습니다.

2. 예를 들어, 스레딩을 사용하여 사용자의 입력을 모니터링하고 백그라운드 작업을 수행하고 입력의 동시 스트림(simultaneous streams)을 처리할 수 있습니다.

3. 스레드에는 다음과 같은 속성이 있습니다.

    1) 스레드를 사용하면 프로그램에서 동시 처리 작업을 수행할 수 있습니다.

    2) .NET Frameowrk System.Threading 네임스페이스를 사용하면 스레드를 더 쉽게 사용할 수 있습니다.

    3) 스레드는 응용 프로그램의 리소스를 공유합니다.

    4) 자세한 내용은 Using Threads and Threading를 참조하십시오.

4. 기본적으로(By default) Visual Basic 또는 C# 프로그램에는 하나의 스레드가 있습니다.

5. 그러나 기본 스레드와 함께 병렬 방식으로(in parallel with the primary thread) 코드를 실행하는 데 사용할 보조 스레드를 만들 수도 있습니다.

6. 이러한 스레드를 일반적으로 작업자 스레드(worker threads)라고 합니다.

7. 작업자 스레드를 사용하면 기본 스레드를 사용(tie up)하지 않고도( 시간이 오래 걸리는(time-consuming) 작업이나 빨리 끝내야 할 작업을 수행할 수 있습니다.

8. 예를 들어, 작업자 스레드는 이전 요청이 완료되기를 기다리지 않고 다른 들어오는 요청(incoming requests)을 처리(fulfill)해야 하는 서버 응용 프로그램에 자주 사용됩니다.

9. 작업자 스레드는 데스크톱 응용 프로그램에서 "백그라운드" 작업을 수행하는 데도 사용됩니다.

10. 이렇게 하면 사용자 인터페이스 요소를 관리하는 기본 스레드가 사용자 작업에 계속 응답(responsive)할 수 있습니다.

11. 스레딩을 사용하면 처리량(throughput)과 응답성(responsiveness)에 관련된 문제를 해결할 수 있지만 교착 상태(deadlock)나 경쟁 조건(race conditions) 등의 리소스 공유 문제(resource-sharing issues)가 새로 발생할 수도 있습니다.

12. 다중 스레드는 파일 핸들과 네트워크 연결 등의 서로 다른 리소스를 필요로 하는 작업에 가장 적합합니다.

13. 한 리소스에 여러 스레드를 할당하면 동기화 문제(synchronization issues)가 발생할 수 있으며 다른 스레드의 작업이 완료되기를 기다리느라 스레드가 자주 차단되며 다중 스레드를 사용하는 의의가 사라집니다.

14. 일반적으로 작업자 스레드는 다른 스레드에 사용되는 리소스를 많이 필요로 하지 않으며 시간이 오래 걸리거나 빠른 시간 안에 끝내야 할 작업을 수행하는 데 사용됩니다.

15. 대개의 경우(Naturally) 프로그램의 일부 리소스는 여러 스레드에서 액세스해야 합니다.

16. 이러한 경우에 대비하여 System.Threading 네임스페이스에서는 스레드를 동기화하기 위한 클래스를 제공합니다.

17. 이러한 클래스에는 Mutex, Monitor, Interlocked, AutoResetEventManualResetEvent가 있습니다.

18. 이러한 클래스의 일부 또는 전체를 사용하여 여러 스레드의 작업을 동기화할 수 있지만 다중 스레딩에 대한 일부 지원 기능은 Visual Basic 및 C# 언어를 통해 지원됩니다.

19. 예를 들어, Visual Basic SyncLock Statement과 C# Lock 문Monitor를 암시적으로 사용하여 동기화 기능을 제공합니다.

Vb.net 쓰레드가 끝날때 까지 기다리는 방법
참고

.NET Framework 4에서는 System.Threading.Tasks.Parallel 및 System.Threading.Tasks.Task 클래스, Parallel LINQ (PLINQ),System.Collections.Concurrent 네임스페이스의 새 동시 컬렉션 클래스, 스레드 개념이 아닌 작업 개념을 기반으로 하는 새 프로그래밍 모델 등을 통해 매우 간단하게 수행할 수 있습니다. 자세한 내용은 Parallel Programming in the .NET Framework을 참조하십시오.

제목

설명

다중 스레드 응용 프로그램(C# 및 Visual Basic)

스레드를 만들고 사용하는 방법에 대해 설명합니다.

다중 스레드 프로시저의 매개 변수 및 반환 값(C# 및 Visual Basic)

다중 스레드 응용 프로그램에서 매개 변수를 전달하고 반환하는 방법에 대해 설명합니다.

연습: BackgroundWorker 구성 요소를 사용한 다중 스레딩(C# 및 Visual Basic)

간단한 다중 스레드 응용 프로그램을 만드는 방법을 보여 줍니다.

스레드 동기화(C# 및 Visual Basic)

스레드의 상호 작용을 제어하는 방법을 설명합니다.

스레드 타이머(C# 및 Visual Basic)

개별 스레드에서 프로시저를 고정 간격으로 실행하는 방법에 대해 설명합니다.

스레드 풀링(C# 및 Visual Basic)

시스템에서 관리하는 작업자 스레드의 풀을 사용하는 방법을 설명합니다.

방법: 스레드 풀 사용(C# 및 Visual Basic)

스레드 풀에 있는 여러 스레드의 동기화된 사용을 보여 줍니다.

Managed Threading

.NET Framework에서 스레딩을 구현하는 방법을 설명합니다.

다중 스레드 응용 프로그램

1. Visual Basic과 C#에서 동시에 여러 작업을 수행하는 응용 프로그램을 만들 수 있습니다.

2. 다른 작업을 지연시킬 가능성이 작업은 별도의 스레드에서 실행되는데, 이러한 프로세스를 다중 스레딩 또는 자유 스레딩이라고 합니다.

3. 다중 스레딩을 사용하는 응용 프로그램에서는 프로세서를 많이 사용하는 작업이(processor-intensive tasks) 별도의 스레드에서 실행되는 동안에도 사용자 인터페이스가 계속 활성 상태에 있기 때문에(stay active) 사용자 입력에 빠르게 응답합니다(more responsive).

4. 또한 다중 스레딩을 사용하면 작업 부하(workload)가 늘어날 때마다 스레드를 추가할 수 있으므로 확장 가능한(scalable) 응용 프로그램을 만드는 경우에도 유용합니다.

Vb.net 쓰레드가 끝날때 까지 기다리는 방법
 참고

Visual Studio 2010 및 .NET Framework 4에서는 새로운 런타임, 새로운 클래스 라이브러리 형식 및 새로운 진단 도구(new diagnostic tools)를 제공하여 병렬 프로그래밍(parallel programming)에 대한 지원이 향상(enhance)되었습니다. 자세한 내용은 .NET Framework의 병렬 프로그래밍을 참조하십시오.

<BackgroundWorker 구성 요소 사용>

5. 다중 스레드 응용 프로그램을 만드는 가장 좋은 방법(The most reliable way)은 BackgroundWorker 구성 요소(component)를 사용하는 것입니다.

6. 이 클래스는 지정하는 메서드만 처리하는 별도의 스레드를 관리합니다.

7. 예제를 보려면 연습: BackgroundWorker 구성 요소를 사용한 다중 스레딩을 참조하십시오.

8. 백그라운드 작업을 시작하려면 BackgroundWorker를 만들고, 작업 진행률(the progress)을 보고하고 작업이 완료되면 신호를 보내는 이벤트를 수신합니다.

9. BackgroundWorker 개체는 프로그래밍 방식으로(programmatically) 또는 도구 상자의 구성 요소 탭에서 폼으로 끌어서 만들 수 있습니다.

10. 폼 디자이너에서 BackgroundWorker를 만들면 구성 요소 트레이에(Component Tray) 표시되며 해당 속성은 속성 창에 표시됩니다.

<백그라운드 작업에 대한 설정>

11. 백그라운드 작업에 대해 설정하려면(set up for) DoWork 이벤트에 대한 이벤트 처리기를 추가해야 합니다.

12. 완료하는 데 시간이 많이 걸리는 작업(time-consuming)을 이 이벤트 처리기로 호출합니다.

13. 작업을 시작하려면 RunWorkerAsync를 호출합니다.

14. 진행률 업데이트 알림(notifications of progress updates)을 받으려면 ProgressChanged 이벤트를 처리합니다.

15. 작업이 완료될 때 알림을 받으려면 RunWorkerCompleted 이벤트를 처리합니다.

16. ProgressChangedRunWorkerCompleted 이벤트를 처리하는 메서드에서 응용 프로그램의 사용자 인터페이스에 액세스할 수 있는데 그 이유는 이러한 이벤트가 RunWorkerAsync메서드를 호출한 스레드에서 발생하기 때문입니다.

17. 그러나 DoWork 이벤트 처리기는 백그라운드 스레드에서 실행되므로 사용자 인터페이스 개체를 다룰 수 없습니다.

<스레드 만들기 및 사용>

18. 응용 프로그램 스레드를 보다 효과적으로 제어하기 위해 스레드를 직접 관리할 수 있습니다.

19. 그러나 올바른 다중 스레드 응용 프로그램을 작성하기가 어려울 수 있습니다.

20. 응용 프로그램이 응답하지 않거나 경합 상태(race conditions)로 인해 일시적인 오류(transient errors)가 발생할 수 있습니다.

21. 자세한 내용은 스레드로부터 안전한 구성 요소를 참조하십시오.

22. Thread 형식의 변수를 선언하고, 새 스레드에서 실행할 프로시저 또는 메서드의 이름을 제공하여 생성자를 호출하면 새 스레드를 만들 수 있습니다.

System.Threading.Thread newThread =
    new System.Threading.Thread(AMethod);

<스레드 시작 및 중지>

23. 새 스레드를 실행하려면 다음 코드와 같이 Start 메서드를 사용합니다.

newThread.Start();

24. 스레드의 실행을 중지하려면 다음 코드와 같이 Abort 메서드를 사용합니다.

newThread.Abort();

25. 스레드의 시작과 중지 이외에도 Sleep 또는 Suspend 메서드를 호출하여 스레드를 일시 중지(pause)하거나, Resume 메서드를 사용하여 일시 중지된 스레드를 다시 시작하거나, Abort 메서드를 사용하여 스레드를 소멸시킬 수 있습니다.

<스레드 메서드>

26. 다음 표에서는 각 스레드를 제어하는 데 사용할 수 있는 몇 가지 메서드를 보여 줍니다.

메서드

동작

Start

스레드 실행을 시작하도록 합니다.

Sleep

지정한 시간 동안 스레드를 일시 중지합니다.

Suspend

스레드가 안전한 지점에 도달하면 스레드를 일시 중지합니다.

Abort

스레드가 안전한 지점에 도달하면 스레드를 중지합니다.

Resume

일시 중지된 스레드를 다시 시작합니다.

Join

현재 스레드에서 다른 스레드가 끝나기를 기다리도록 만듭니다. 이 메서드는 제한 시간 값(a time-out value)과 함께 사용할 경우 스레드가 할당된 시간 안에 끝나면 True를 반환합니다.

<안전한 지점>

27. 이러한 메서드의 대부분은 이름 그대로이므로 설명이 필요 없지만(self-explanatory) 안전한 지점(safe points)이라는 개념은 사용자에게 새로울 수 있습니다.

28. 안전한 지점은 공용 언어 런타임이 사용되지 않은 변수를 해제하고 메모리를 회수(reclaim)하는 프로세스인 자동 가비지 수집을 수행하기에 안전한 코드 위치를 말합니다.

29. 스레드의 Abort 또는 Suspend 메서드를 호출하면 공용 언어 런타임에서 코드를 분석하여 스레드 실행을 중지할 적절한 위치를 결정합니다.

<스레드 속성>

Property

IsAlive

스레드가 활성 상태이면 True 값을 포함합니다.

IsBackground

스레드가 백그라운드 스레드인지 또는 백그라운드 스레드이어야 하는지 여부를 나타내는 부울 값을 가져오거나 설정합니다. 백그라운드 스레드는 포그라운드 스레드와 유사하지만 프로세스가 중지되는 것은 막지 않습니다. 한 프로세스에 속하는 포그라운드 스레드가 모두 중지되고 나면 공용 언어 런타임에서는 아직 활성화되어 있는 백그라운드 스레드에 대해 Abort 메서드를 호출하여 해당 프로세스를 끝냅니다.

Name

스레드의 이름을 가져오거나 설정합니다. 이 속성은 대개 디버깅할 때 개별 스레드를 찾는 데 사용됩니다.

Priority

운영 체제에서 스레드 예약(scheduling)의 우선 순위를 결정(prioritize)하는 데 사용되는 값을 가져오거나 설정합니다.

ApartmentState

특정 스레드에 대해 사용되는 스레딩 모델을 가져오거나 설정합니다. 스레드가 비관리 코드를 호출할 때는 스레딩 모델이 중요합니다.

ThreadState

스레드의 상태를 설명하는 값을 포함합니다.

<스레드 우선 순위>

30. 모든 스레드에는 해당 스레드가 실행되는 데 걸리는 프로세서 시간을 결정하는 우선 순위 속성이 있습니다.

31. 운영 체제에서는 우선 순위가 낮은 스레드(low-priority threads)보다 높은 스레드(high-priority)에 더 긴 시간 간격(slices)을 할당합니다.

32. 새 스레드는 Normal 값으로 만들어지지만 ThreadPriority 열거형(enumeration)에 있는 다른 값으로 Priority 속성을 변경할 수 있습니다.

33. 다양한 스레드 우선 순위에 대한 자세한 내용(a detailed description)은 ThreadPriority를 참조하십시오.

<포그라운드 및 백그라운드 스레드>

34. 포그라운드 스레드는 무기한(indefinitely) 실행되는 반면(whereas) 백그라운드 스레드는 마지막 포그라운드 스레드가 중지되면 곧바로 중지됩니다.

35. IsBackground 속성을 사용하면 스레드의 백그라운드 상태를 확인(determine)하거나 변경할 수 있습니다.

<폼 및 컨트롤에 다중 스레딩 사용>

36. 다중 스레딩은 프로시저와 클래스 메서드를 실행하는 데 가장 적합하지만(best suited to) 폼 및 컨트롤에도 사용될 수 있습니다.

37. 이 경우 다음 사항을 주의해야 합니다.

    1) 가능한 한 컨트롤을 만들 때 컨트롤이 생성된 스레드에서만 컨트롤의 메서드를 실행합니다. 컨트롤 메서드를 다른 스레드에서 호출해야 하는 경우에는 Invoke를 사용하여 메서드를 호출해야 합니다.

    2) 컨트롤이나 폼을 조작(manipulate)하는 스레드는 SyncLock(Visual Basic) 또는 lock(C#) 문을 사용하여 잠그지 않습니다. 컨트롤의 메서드와 폼의 메서드는 종종 호출 프로시저로 콜백되기 때문에 실수로(inadvertently) 교착 상태(두 개의 스레드가 서로 다른 쪽의 스레드 잠금이 해제되기를 기다림으로써 응용  프로그램이 중단되는 상태)가 발생할 수 있습니다.

다중 스레드 프로시저의 매개 변수 및 반환 값

1. 다중 스레드 응용 프로그램에서 값을 지정하고 반환하는 것은 복잡합니다.

2. 그 이유는 사용하지 않고 값을 반환하지도 않는 프로시저에 데한 참조가 스레드 클래스에 대한 생성자에 전달되어야 하기 때문입니다.

3. 다음 단원(sections)에서는 별도의 스레드에 있는 프로시저에서 매개 변수를 지정하고 값을 반환하는 방법에 대해 간단하게 설명합니다.

<다중 스레드 프로시저에 대한 매개 변수 지정>

4. 다중 스레드 메서드의 호출에 대해 매개 변수를 지정하는 가장 좋은 방법은 클래스의 대상 메서드를 래핑(wrap)하고 새 스레드에 대해 매개 변수로 사용될 해당 클래스에 대한 필드를 정의하는 것입니다.

5. 이 방법을 사용하면 새 스레드를 시작할 때마다 클래스의 새 인스턴스와 해당 매개 변수를 만들 수 있습니다.

6. 예를 들어 다음과 같이 삼각형(triangle)의 면적을 계산하는 함수가 있다고 가정합니다.

double CalcArea(double Base, double Height)
{
    return 0.5 * Base * Height;
}

7. 이 경우 다음과 같이 CalcArea 함수를 래핑하고 입력 매개 변수를 저장하기 위한 필드를 만드는 클래스를 작성할 수 있습니다.

class AreaClass
{
    public double Base;
    public double Height;
    public double Area;
    public void CalcArea()
    {
        Area = 0.5 * Base * Height;
        MessageBox.Show("The area is: " + Area.ToString());
    }
}

8. AreaClass를 사용하려면 AreaClass 개체를 만들고, 다음 코드와 같이 Base 속성과 Height 속성을 설정합니다.

protected void TestArea()
{
    AreaClass AreaObject = new AreaClass();

    System.Threading.Thread Thread =
        new System.Threading.Thread(AreaObject.CalcArea);
    AreaObject.Base = 30;
    AreaObject.Height = 40;
    Thread.Start();
}

9. 이 때 TestArea 프로시저는 CalcArea 메서드를 호출한 후 Area 필드의 값을 확인하지 않습니다.

10. CalcArea는 별도의 스레드에서 실행되므로 Thread.Start를 호출한 직후에 Area 필드를 확인하면 Area 필드가 설정되어 있지 않을 수도 있습니다.

12. 다음 단원에서는 다중 스레드 프로시저에서 값을 반환할 수 있는 좀 더 유용한 방법에 대해 설명합니다.

<다중 스레드 프로시저에서 값 반환>

13. 프로시저는 함수가 될 수 없으며 ByRef 인수를 사용할 수도 없기 때문에 별도의 스레드에서 실행되는 프로시저에서 값을 반환하는 것은 복잡합니다(complicated).

14. 값을 반환하는 가장 쉬운 방법(the easiest way)은 BackgroundWorker 구성 요소를 사용하여 스레드를 관리하고, 작업이 완료되면 이벤트를 발생시키고(raise an event), 이벤트 처리기로 처리하는 것입니다.

15. 다음 예제에서는 별도의 스레드에서 실행되는 프로시저에서 이벤트를 발생시키는 방법으로 값을 반환합니다.

class AreaClass2
{
    public double Base;
    public double Height;
    public double CalcArea()
    {
        // Calculate the area of a triangle.
        return 0.5 * Base * Height;
    }
}

private System.ComponentModel.BackgroundWorker BackgroundWorker1
    = new System.ComponentModel.BackgroundWorker();

private void TestArea2()
{
    InitializeBackgroundWorker();

    AreaClass2 AreaObject2 = new AreaClass2();
    AreaObject2.Base = 30;
    AreaObject2.Height = 40;

    // Start the asynchronous operation.
    BackgroundWorker1.RunWorkerAsync(AreaObject2);
}

private void InitializeBackgroundWorker()
{
    // Attach event handlers to the BackgroundWorker object.
    BackgroundWorker1.DoWork +=
        new System.ComponentModel.DoWorkEventHandler(BackgroundWorker1_DoWork);
    BackgroundWorker1.RunWorkerCompleted +=
        new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
}

private void BackgroundWorker1_DoWork(
    object sender,
    System.ComponentModel.DoWorkEventArgs e)
{
    AreaClass2 AreaObject2 = (AreaClass2)e.Argument;
    // Return the value through the Result property.
    e.Result = AreaObject2.CalcArea();
}

private void BackgroundWorker1_RunWorkerCompleted(
    object sender,
    System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    // Access the result through the Result property.
    double Area = (double)e.Result;
    MessageBox.Show("The area is: " + Area.ToString());
}

16. QueueUserWorkItem 메서드의 선택적 상태 개체(state-object) 변수인 ByVal을 사용하면 스레드 풀 스레드에 매개 변수와 반환 값을 제공할 수 있습니다.

17. 스레드 타이머 스레드에서도 이러한 용도의 상태 개체를 지원합니다.

18. 스레드 풀링 및 스레드 타이머에 대한 자세한 내용은 스레드 풀링스레드 타이머를 참조하십시오.

연습: BackgroundWorker 구성 요소를 사용한 다중 스레딩

1. 이 연습에서는 텍스트 파일에서 특정 단어를 검색하는 다중 스레드 응용 프로그램을 만드는 방법을 설명합니다.

    1) BackgroundWorker 구성 요소에서 호출할 수 있는 메서드로 클래스 정의

    2) BackgroundWorker 구성 요소에서 발생한(raise) 이벤트 처리

    3) BackgroundWorker 구성 요소를 시작하여 메서드 실행

    4) BackgroundWorker 구성 요소를 중지하는 Cancel 단추 구현

<사용자 인터페이스를 만들려면>

2. Visual Basic 또는 C# Windows 응용 프로그램 프로젝트를 새로 열고 Form1이라는 폼을 만듭니다.

3. Form1에 두 개의 단추와 네 개의 텍스트 상자를 추가합니다.

4. 다음 표에 표시된 대로 개체를 명명합니다.

object

Property

설정

첫 번째 단추

NameText

Start, Start

두 번째 단추

NameText

Cancel, Cancel

첫 번째 텍스트 상자

NameText

SourceFile, ""

두 번째 텍스트 상자

NameText

CompareString, ""

세 번째 텍스트 상자

NameText

WordsCounted, "0"

네 번째 텍스트 상자

NameText

LinesCounted, "0"

5. 각 텍스트 상자 옆에 레이블을 추가합니다.

6. 다음 표와 같이 각 레이블에 Text 속성을 설정합니다.

object

Property

설정

첫 번째 레이블

Text

소스 파일

두 번째 레이블

Text

Compare String

세 번째 레이블

Text

Matching Words

네 번째 레이블

Text

Lines Counted

<BackgroundWorker 구성 요소를 만들고 해당 이벤트를 구독하려면>

7. 도구 상자의 구성 요소 섹션에 있는 BackgroundWorker 구성 요소를 폼에 추가합니다

8. 이 구성 요소가 폼의 구성 요소 트레이에 나타납니다.

9. Visual Basic의 BackgroundWorker1 개체나 C#의 backgroundWorker1 개체에 대해 다음 속성을 설정합니다.

Property

설정

WorkerReportsProgress

True

WorkerSupportsCancellation

True

10. C#에서만 backgroundWorker1 개체의 이벤트를 구독합니다.

11. 속성 창의 위쪽에서(at the top of the Properties window) 이벤트 아이콘을 클릭합니다.

12. RunWorkerCompleted 이벤트를 두 번 클릭하여 이벤트 처리기 메서드(event handler method)를 만듭니다.

13. ProgressChanged 및 Dowork 이벤트에 대해 동일한 작업을 수행합니다.

<개별 스레드에서 실행되는 메서드를 정의하려면>

14. 프로젝트 메뉴에서 클래스 추가를 선택하여 프로젝트에 클래스를 추가합니다.

15. 새 항목 추가(Add New Item) 대화 상자가 표시됩니다.

16. 템플릿 창에서 클래스를 선택하고 이름 필드에 Words.vb나 Words.cs를 입력합니다.

17. 추가를 클릭합니다.

18. Words 클래스가 표시됩니다.

19. Words 클래스에 다음 코드를 추가합니다.

public class Words
{
    // Object to store the current state, for passing to the caller.
    public class CurrentState
    {
        public int LinesCounted;
        public int WordsMatched;
    }

    public string SourceFile;
    public string CompareString;
    private int WordCount;
    private int LinesCounted;

    public void CountWords(
        System.ComponentModel.BackgroundWorker worker,
        System.ComponentModel.DoWorkEventArgs e)
    {
        // Initialize the variables.
        CurrentState state = new CurrentState();
        string line = "";
        int elapsedTime = 20;
        DateTime lastReportDateTime = DateTime.Now;

        if (CompareString == null ||
            CompareString == System.String.Empty)
        {
            throw new Exception("CompareString not specified.");
        }

        // Open a new stream.
        using (System.IO.StreamReader myStream = new System.IO.StreamReader(SourceFile))
        {
            // Process lines while there are lines remaining in the file.
            while (!myStream.EndOfStream)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    line = myStream.ReadLine();
                    WordCount += CountInString(line, CompareString);
                    LinesCounted += 1;

                    // Raise an event so the form can monitor progress.
                    int compare = DateTime.Compare(
                        DateTime.Now, lastReportDateTime.AddMilliseconds(elapsedTime));
                    if (compare > 0)
                    {
                        state.LinesCounted = LinesCounted;
                        state.WordsMatched = WordCount;
                        worker.ReportProgress(0, state);
                        lastReportDateTime = DateTime.Now;
                    }
                }
                // Uncomment for testing.
                //System.Threading.Thread.Sleep(5);
            }

            // Report the final count values.
            state.LinesCounted = LinesCounted;
            state.WordsMatched = WordCount;
            worker.ReportProgress(0, state);
        }
    }


    private int CountInString(
        string SourceString,
        string CompareString)
    {
        // This function counts the number of times
        // a word is found in a line.
        if (SourceString == null)
        {
            return 0;
        }

        string EscapedCompareString =
            System.Text.RegularExpressions.Regex.Escape(CompareString);

        System.Text.RegularExpressions.Regex regex;
        regex = new System.Text.RegularExpressions.Regex( 
            // To count all occurrences of the string, even within words, remove
            // both instances of @"\b" from the following line.
            @"\b" + EscapedCompareString + @"\b",
            System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        System.Text.RegularExpressions.MatchCollection matches;
        matches = regex.Matches(SourceString);
        return matches.Count;
    }

}

<스레드에서 이벤트를 처리하려면>

20. 기본 폼에 다음 이벤트 처리기를 추가합니다.

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// This event handler is called when the background thread finishes.
// This method runs on the main thread.
if (e.Error != null)
    MessageBox.Show("Error: " + e.Error.Message);
else if (e.Cancelled)
    MessageBox.Show("Word counting canceled.");
else
    MessageBox.Show("Finished counting words.");
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // This method runs on the main thread.
    Words.CurrentState state =
        (Words.CurrentState)e.UserState;
    this.LinesCounted.Text = state.LinesCounted.ToString();
    this.WordsCounted.Text = state.WordsMatched.ToString();
}

<WordCount 메서드를 실행하는 새 스레드를 시작하여 호출하려면>

21. 프로그램에 다음 프로시저를 추가합니다.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    // This event handler is where the actual work is done.
    // This method runs on the background thread.

    // Get the BackgroundWorker object that raised this event.
    System.ComponentModel.BackgroundWorker worker;
    worker = (System.ComponentModel.BackgroundWorker)sender;

    // Get the Words object and call the main method.
    Words WC = (Words)e.Argument;
    WC.CountWords(worker, e);
}

private void StartThread()
{
    // This method runs on the main thread.
    this.WordsCounted.Text = "0";

    // Initialize the object that the background worker calls.
    Words WC = new Words();
    WC.CompareString = this.CompareString.Text;
    WC.SourceFile = this.SourceFile.Text;

    // Start the asynchronous operation.
    backgroundWorker1.RunWorkerAsync(WC);
}

22. 해당 폼의 Start 단추에서 StartThread 메서드를 호출합니다.

private void Start_Click(object sender, EventArgs e)
{
    StartThread();
}

<스레드를 중지하는 Cancel 단추를 구현하려면>

23. Cancel 단추에 대한 Click 이벤트 핸들러에서 StopThread 프로시저를 호출합니다.

private void Cancel_Click(object sender, EventArgs e)
{
    // Cancel the asynchronous operation.
    this.backgroundWorker1.CancelAsync();
}

<테스트>

24. 이제 응용 프로그램을 테스트하여 제대로 작동되는지 확인할 수 있습니다(make sure it works correctly).

<응용 프로그램을 실행하려면>

    1) F5 키를 눌러 응용 프로그램을 실행합니다.

    2) 폼이 표시되면 sourceFile 상자에 테스트하려는 파일의 파일 경로를 입력합니다.

    3) 예를 들어, 테스트 파일 이름이 Test.txt일 경우 C:\Test.txt라고 입력합니다.

    4) 두 번째 텍스트 상자에 텍스트 파일에서 응용 프로그램이 검색할 단어(a word)나 구(a phrase)를 입력합니다.

    5) Start 단추를 클릭합니다.

    6) LinesCounted 단추의 숫자가 즉시 증가됩니다.

    7) 이 작업이 끝나면 응용 프로그램은 "Finished Counting" 메시지를 표시합니다.

<Cancel 단추를 테스트하려면>

    1) F5 키를 눌러 응용 프로그램을 시작하고 이전 프로시저에서 설명한 대로 파일 이름과 검색 단어를 입력합니다.

    2) 이 작업이 끝나기 전에 프로시저를 취소할 수 있는 시간이 있는지를 확인하기 위해 선택한 파일의 크기가 충분한지 확인합니다.

    3) Start 단추를 클릭하여 응용 프로그램을 시작합니다.

    4) Cancel 단추를 클릭합니다.

    5) 응용 프로그램이 즉시 카운트를 중지합니다.

<다음 단계>

25. 이 응용 프로그램에는 기본적인 오류 처리 기능(some basic error handling)이 포함되어 있습니다.

26. 빈 검색 문자열을 검색합니다(detect).

27. 카운트할 수 있는 최대 단어 수(the maximum number)나 줄 수를 초과하는 오류 등을 포함한 여러 다른 오류들을 처리하여 이 프로그램을 보다 강력하게(robust) 만들 수 있습니다.

스레드 동기화

1. 다음 단원에서는 다중 스레드 응용 프로그램에서 리소스에 대한 액세스를 동기화하는 데 사용할 수 있는 기능과 클래스에 대해 설명합니다.

2. 응용 프로그램에서 다중 스레드를 사용할 때의 이점(benefits) 중 하나는 각 스레드가 비동기적으로 실행된다는 점입니다.

3. Windows 응용 프로그램의 경우 이렇게 하면 응용 프로그램 창과 컨트롤의 응답 가능 상태를 유지한 채(remain resonsive) 시간이 오래 걸리는 작업을 백그라운드에서 수행할 수 있습니다.

4. 서버 응용 프로그램의 경우 다중 스레딩을 사용하면 들어오는 각 요청을  서로 다른 스레드로 처리할 수 있습니다.

5. 그렇지 않으면(otherwise), 이전 요청이 완전히 처리될 때까지(fully satisfied) 새로운 각 요청의 처리를 시작할 수 없습니다(not get serviced).

6. 그러나 스레드의 비동기적 특성(nature)으로 인해 파일 핸들, 네트워크 연결, 메모리 등과 같은 리소스에 대한 액세스를 조정(coordinate)해야 한다는 문제가 있습니다.

7. 그렇지 않으면 두 개  이상의 스레드에서 각각 다른 스레드의 작업을 인식하지 못한 채(unaware of) 동시에(at the same time) 동일한 리소스에 액세스할 수 있습니다.

8. 그 결과로 예기치 않은(unpredictable) 데이터 손상(data corruption)이 발생할 수 있습니다.

9. 정수 숫자 데이터 형식(integral numeric data types)에 대한 간단한 연산의 경우 Interlocked 클래스의 멤버를 통해 스레드를 동기화할 수 있습니다.

10. 다른 모든 데이터 형식과 스레드로부터 안전하게 보호되지 않는 리소스의 경우 다중 스레딩을 안전하게 수행하려면 이 항목에서 설명하는 구문(the constructs)을 사용해야 합니다.

11. 다중 스레드 프로그래밍에 대한 배경 지식은 다음을 참조하십시오.

    1) 관리되는 스레딩 기본 사항

    2) 스레드 및 스레딩 사용

    3) 관리되는 스레딩을 구현하는 최선의 방법

<잠금 및 SyncLock 키워드>

12. lock(C#) 및 SyncLock(Visual Basic) 문을 사용하면 다른 스레드의 방해(interruption)를 받지 않은 채 코드 블록의 실행을 완료(run to completion)할 수 있습니다.

13. 이를 위해서는 코드 블록을 진행하는 동안 지정된 개체에 대한 상호 배타적 잠금(a mutual-exclusion lock)을 유지해야 합니다.

14. lock 또는 SyncLock 문은 개체를 인수로 지정하며 뒤에 한 번에 하나의 스레드에서만 실행할 코드 블록이 나옵니다.

public class TestThreading
{
    private System.Object lockThis = new System.Object();

    public void Process()
    {

        lock (lockThis)
        {
            // Access thread-sensitive resources.
        }
    }

}

15. lock 키워드에 제공되는 인수는 참조 형식을 기반으로 한 개체여야  하고 이 개체는 잠금 범위를 정의하는 데 사용됩니다.

16. 위 예제에서 함수 외부에 lockThis 개체에 대한 참조가 없으므로 잠금 범위는 이 함수로 제한됩니다.

17. 이와 같은 참조가 있는 경우에만 잠금 범위가 해당 개체애 맞게 확대됩니다(extend).

18. 엄밀하게 말해서(strictly speaking), 제공된 객체는 여러 스레드 간에 공유되는 리소스를 고유하게(uniquely) 식별하는 데만(identify) 사용되므로 이는 임의의(arbitrary) 클래스 인스턴스가 될 수 있습니다.

19. 그러나 실제로(In practice) 코드를 작성하는 경우 이 개체는 일반적으로 스레드 동기화(thread synchronization)가 필요한 리소스를 나타냅니다.

20. 예를 들어, 컨테이너 개체를 여러 스레드에서 사용해야 하는 경우 이 컨테이너를 lock 키워드에 전달하고 동기화된 코드 블록을 그 뒤에 추가하여 컨테이너에 액세스할 수 있습니다.

21. 다른 스레드는 동일한 컨테이너에 대해 잠긴 상태이므로 이 개체에 액세스할 수 없고 개체에 대한 액세스가 안전하게 동기화됩니다.

22. 일반적으로 public 형식이나 사용자 응용 프로그램의 제어 범위 밖에 있는 개체 인스턴스에 대해서는 잠금을 사용하지 않는 것이 좋습니다.

23. 예를 들어, 인스턴스에 공용으로 액세스할 수 있는 경우 lock(this)을 사용하면 문제가 발생할 수 있습니다(problematic).

24. 제어 범위 밖에 있는 코드마저 개체에 대해 잠길 수 있기 때문입니다.

25. 이 경우 동일한 개체가 해제되기를(the release of the same object) 두 개 이상의 스레드가 기다리는 교착 상태(deadlock situations)가 발생할 수 있습니다.

26. 개체와 달리(as opposed to) 공용 데이터 형식에 대해 잠금을 수행하는 경우에도 동일한 이유로 인해 문제가 발생할 수 있습니다.

27. 리터럴 문자열에 대해 잠금(Locking on literal strings)을 수행하는 경우는 특히 위험(risky)합니다.

28. 리터럴 문자열은 CLR(공용 언어 런타임)에서 사용하도록 의도되어 있기 때문입니다.

29. 즉, 전체 프로그램에서 임의의 지정된 문자열에 대한 인스턴스가 하나 있으며 정확하게 동일한 개체는 실행 중인 모든 응용 프로그램 도메인에서 모든 스레드에 대해 이 리터럴을 나타냅니다.

30. 그 결과, 응용 프로그램 프로세스에서 내용이 동일한 문자열을 잠그면 응용 프로그램에서 해당 문자열의 인스턴스가 모두 잠깁니다.

31. 따라서 잠금은 억류(intern)되지 않은 private 또는 protected버에 대해 수행하는 것이 좋습니다.

32. 일부 클래스는 잠금을 위한 특별한 멤버를 제공합니다.

33. 예를 들어 Array 형식은 SyncRoot를 제공합니다.

34. 대부분의 SyncRoot 멤버도 제공합니다.

35. lock 및 SyncLock 문에 대한 자세한 내용은 다음 항목을 참조하십시오.

    1) lock 문

    2) SyncLock 문

    3) Monitor

<Monitor>

36. lock 및 SyncLock 키워드와 마찬가지로 monitor를 사용하면 코드 블록이 여러 스레드에서 동시에 실행(simultaneous execution)되지 않도록 방지(prevent)할 수 있습니다.

37. Enter 메서드를 사용하면 스레드 하나만 다음 문으로 진행하도록(proceed into) 허용할 수 있습니다.

38. 모든 다른 스레드는 현재 실행 중인 스레드가 Exit를 호출할 때까지 차단됩니다.

39. 이는 lock 키워드를 사용할 때와 동일합니다.

lock (x)
{
    DoSomething();
}
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}

40. 일반적으로 Monitor 클래스를 직접 사용하는 것보다 lock(C#) 또는 SyncLock(Visual Basic) 키워드를 사용하는 것이 더 좋습니다.

41. lock or SyncLock 키워드를 사용하면 코드를 더 간결하게(more concise) 작성할 수 있고 lock 또는 SyncLock 키워드의 경우 보호된 코드에서 예외를 throw 하더라도 내부 모니터를(the underlying monitor) 해제할 수 있기 때문입니다.

42. 이를 수행하는 데는 finally 키워드가 사용됩니다.

43. 이 키워드는 예외가 throw되었는지 여부와 상관없이 관련 코드 블록을 실행합니다.

<동기화 이벤트 및 대기 핸들>

44. lock 또는 monitor를 사용하면 스레드가 중요한 부분을 차지하는 코드 블록이 동시에 실행되지 않도록 방지할 수 있지만 이러한 구문을 사용하면 한 스레드가 다른 스레드에 이벤트를 전달(communicate)할 수 없습니다.

45. 이 문제를 해결하기 위해서는 스레드를 활성화(activate)하거나 일시 중단(suspend)하는 데 사용할 수 있고 신호를 받은(signaled) 상태 및 신호를 받지 않은(un-signaled) 상태 중 한 가지 상태가 지정되는 개체인 동기화 이벤트(synchronization events))가 필요합니다.

46. 스레드를 일시 중단하려면 신호를 받지 않은 상태의 동기화 이벤트에서 스레드를 대기시키고, 스레드를 활성화하려면 신호를 받은 상태로 이벤트 상태를 변경합니다.

47. 이미 신호를 받은 상태의 이벤트에서 스레드를 대기시키려고 하면 스레드가 지연 시간 없이 계속 실행됩니다.

48. 동기화 이벤트에는 AutoResetEventManualResetEvent라는 두 가지 종류가 있습니다.

49. 이 둘 사이의 유일한 차이는 AutoResetEvent의 경우 스레드를 활성화할 때마다 신호를 받은 상태에서 신호를 받지 않은 상태로 자동으로(automatically) 변경된다는 점입니다.

50. 반대로(Conversely), ManualResetEvent를 사용하면 신호를 받은 상태를 통해 스레드를 그 수에 상관없이(any number of threads) 활성화할 수 있고 해당 Reset 메서드를 호출한 경우에만 신호를 받지 않은 상태로 되돌릴 수(revert) 있습니다.

51. 스레드는 WaitOne, WaitAny 또는 WaitAll 등의 대기 메서드 중 하나를 호출하여 이벤트에 대기할 수 있도록 합니다.

52. WaitHandle.WaitOne()은 단일 이벤트가 신호를 받을 때까지(become signaled) 스레드를 대기시키고, WaitHandel.WaitAny()는 하나 이상의 지정된(indicated) 이벤트가 신호를 받을 때까지 스레드를 차단하며 WaitHandle.WaitAll()은 지정된 모든 이벤트가 신호를 받을 때까지 스레드를 차단합니다.

53. 이벤트는 해당 Set 메서드가 호출되면 신호를 받은 상태로 변경됩니다.

54. 다음 예제에서는 Main 함수를 사용하여 스레드를 만들고 시작합니다.

55. 새 스레드는 WaitOne 메서드를 사용하여 이벤트에서 대기합니다.

56. Main 함수를 실행하는 기본 스레드(primary thread)를 통해 이벤트가 신호를 받은 상태가 될 때까지 이 스레드는 일시 중단됩니다.

57. 이벤트가 신호를 받은 상태가 되면 보조 스레드(the auxiliary thread)가 반환됩니다.

58. 이 경우(In this case), 이벤트는 스레드 하나의 활성화에만 사용되므로 AutoResetEvent 또는 ManualResetEvent 클래스를 사용할 수 있습니다.

using System;
using System.Threading;

class ThreadingExample
{
    static AutoResetEvent autoEvent;

    static void DoWork()
    {
        Console.WriteLine("   worker thread started, now waiting on event...");
        autoEvent.WaitOne();
        Console.WriteLine("   worker thread reactivated, now exiting...");
    }

    static void Main()
    {
        autoEvent = new AutoResetEvent(false);

        Console.WriteLine("main thread starting worker thread...");
        Thread t = new Thread(DoWork);
        t.Start();

        Console.WriteLine("main thread sleeping for 1 second...");
        Thread.Sleep(1000);

        Console.WriteLine("main thread signaling worker thread...");
        autoEvent.Set();
    }
}

<뮤텍스 개체>

59. 뮤텍스는 monitor와 비슷합니다.

60. 이는 한 번에(at a time) 여러 스레드에서(more than one thread) 코드 블록이 동시에 실행되는 것을 방지합니다.

61. 사실(In fact), "뮤텍스"라는 용어(name)는 "상호 배타적(mutually exclusive)"이라는 표현의 줄임말(a shortened form)입니다.

62. 그러나 monitor와 달리 뮤텍스를 사용하면 프로세스 간에 스레드를 동기화(synchronize)할 수 있습니다.

63. 뮤텍스는 Mutex 클래스로 표현(represented)됩니다.

64. 프로세스간 동기화(inter-process synchronization)에 사용되는 뮤텍스를 명명된 뮤텍스라고 합니다.

65. 이는 다른 응용 프로그램에 사용하기 위한 것이며 전역 또는 정적 변수를 통해 공유할 수 없기 때문입니다.

66. 두 응용 프로그램에서 모두 동일한 뮤텍스 개체에 액세스할 수 있도록 이 뮤텍스에 이름을 지정해야 합니다.

67. 프로세스 내의 스레드(intra-process thread synchronization)를 동기화하는 데 뮤텍스를 사용할 수도 있지만 일반적으로 Monitor를 사용하는 것이 더 좋습니다.

68. monitor는 .NET Framework용으로 특별히 디자인 되었으며 리소스를 더 효율적으로 활용(make better use)하기 때문입니다.

69. 반면(In contrast) Mutex 클래스는 Win32 구문에 대한 래퍼(a wrapper to a Win32 construct)입니다.

70. 이는 monitor 보다 더 강력하지만 뮤텍스를 사용하려면 Monitor 클래스에 필요한 것보다 더 처리가 복잡한(more computationally expensive) interop 전환(transition)이 필요합니다.

71. 뮤텍스를 사용하는 방법의 예제는 뮤텍스를 참조하십시오.

<Interlocked 클래스>

72. Interlocked 클래스의 메서드를 사용하면 여러 스레드에서 같은 값을 동시에 업데이트 하거나 비교하려고 할 때 발생할 수 있는 문제를 방지할 수 있습니다.

73. 이 클래스의 메서드를 사용하면 모든 스레드에서 값을 안전하게 늘리거나(increment), 줄이거나(decrement), 교환(exchange)하거나 비교(compare)할 수 있습니다.

<ReadWriter 잠금>

74. 일부 경우에는(In some cases) 데이터를 쓰고 있을 때만 리소스를 잠그고 데이터를 업데이트하지 않을 때는 여러 클라이언트에서 동시에 데이터를 읽을 수 있도록 할 수 있습니다.

75. ReadWriterLock 클래스를 사용하면 스레드에서 리소스를 수정하는 동안은 리소스를 단독으로 사용하고(exclusive access), 리소스를 읽을 때는 여러 스레드에서 동시에 사용하도록(non-exclusive access) 할 수 있습니다.

76. ReaderWriter 잠금은 해당 스레드에서 데이터를 업데이트할 필요가 없는 경우에도 다른 스레드를 대기하도록 만드는 단독 잠금 대신 사용할 수 있는 유용한 기능입니다.

<교착 상태>

77. 스레드 동기화는 다중 스레드 응용 프로그램에서 매우 중요하지만(invaluable) 여러 스레드가 서로를 대기하여 응용 프로그램이 중지되는(come to a halt) deadlock이 발생할 위험이 항상 존재합니다.

78. 교착 상태는 교차로에서(at a four-way stop) 자동차들이 서로 다른 자동차가 가기를 기다리며 모두 멈춰있는 상황과 비슷합니다(is analogous to).

79. 따라서 교착 상태를 방지하는 것이 중요하며 이를 위해서는 철저한 계획(careful planning)을 세워야 합니다.

80. 종종 코딩을 시작하기 전에 다중 스레드 응용 프로그램의 다이어그램을 작성하면(by diagramming) 교착 상태를 예측할 수 있습니다.

<관련 단원>

1. 방법: 스레드 풀 사용

2. 방법: Visual C# .NET을 사용하여 다중 스레딩 환경에서 공유 리소스에 대한 액세스 동기화

3. 방법: Visual C# .NET을 사용하여 스레드 만들기

4. Visual C#을 사용하여 작업 항목을 스레드 풀에 제출하는 방법

5. 방법: Visual C# .NET을 사용하여 다중 스레딩 환경에서 공유 리소스에 대한 액세스 동기화

스레드 타이머

1. System.Threading.Timer 클래스는 작업을 별도의 스레드에서 정기적으로(periodically) 실행하는 데 유용합니다.

2. 예를 들어, 데이터베이스의 상태(status) 및 무결성(integrity)을 검사하거나 중요한(critical) 파일을 백업할 때 스레드 타이머를 사용할 수 있습니다.

3. 다음 예제에서는 2초마다 작업을 시작하고 플래그를 사용하여 타이머를 중지하는 Dispose 메서드를 시작합니다(initiate).

4. 이 예제에서는 출력 창(the output window)에 상태(status)를 게시(post)합니다.

private class StateObjClass
{
    // Used to hold parameters for calls to TimerTask.
    public int SomeValue;
    public System.Threading.Timer TimerReference;
    public bool TimerCanceled;
}

public void RunTimer()
{
    StateObjClass StateObj = new StateObjClass();
    StateObj.TimerCanceled = false;
    StateObj.SomeValue = 1;
    System.Threading.TimerCallback TimerDelegate =
        new System.Threading.TimerCallback(TimerTask);

    // Create a timer that calls a procedure every 2 seconds.
    // Note: There is no Start method; the timer starts running as soon as 
    // the instance is created.
    System.Threading.Timer TimerItem =
        new System.Threading.Timer(TimerDelegate, StateObj, 2000, 2000);

    // Save a reference for Dispose.
    StateObj.TimerReference = TimerItem;  

    // Run for ten loops.
    while (StateObj.SomeValue < 10) 
    {
        // Wait one second.
        System.Threading.Thread.Sleep(1000);  
    }

    // Request Dispose of the timer object.
    StateObj.TimerCanceled = true;  
}

private void TimerTask(object StateObj)
{
    StateObjClass State = (StateObjClass)StateObj;
    // Use the interlocked class to increment the counter variable.
    System.Threading.Interlocked.Increment(ref State.SomeValue);
    System.Diagnostics.Debug.WriteLine("Launched new thread  " + DateTime.Now.ToString());
    if (State.TimerCanceled)    
    // Dispose Requested.
    {
        State.TimerReference.Dispose();
        System.Diagnostics.Debug.WriteLine("Done  " + DateTime.Now.ToString());
    }
}

4. 스레드 타이머는 콘솔 응용 프로그램을 개발할 때와 같이 System.Windows.Forms.Timer 개체를 사용할 수 없는 경우에 특히 유용합니다.

스레드 풀링

1. 스레드 풀은 백그라운드에서 여러 가지 작업을 수행하는 데 사용할 수 있는 스레듸의 컬렉션입니다.

2. 자세한 내용은 스레딩을 참조하십시오.

3. 스레드 풀을 사용하면 기본 스레드(the primary thread)에서 다른 작업을 비동기적으로 수행할 수 있습니다.

4. 스레드 풀은 대개 서버 응용 프로그램에 사용됩니다.

5. 들어오는 각 요청은(Each incoming request) 스레드 풀의 스레드에 할당되므로 기본 스레드를 사용할 수 있을 때까지 기다리거나 이후의 요청을(subsequent requests) 처리하는 데 시간을 지연하지 않고 요청을 비동기적으로 처리할 수 있습니다.

6. 풀에 있는 스레드가 해당 작업을 완료하고 대기 스레드의 큐(a queue of waiting threads)로 반환되면 여기서 해당 스레드를 다시 사용(reuse)할 수 있습니다.

7. 이와 같이 스레드를 다시 사용하면 응용 프로그램에서 각 작업에 대해 새 스레드를  만드느라 리소스를 낭비하지 않아도 됩니다.

8. 스레드 풀에는 일반적으로 스레드의 최대 수가 지정되어 있습니다.

9. 모든 스레드에서 작업을 수행 중이면 다른 작업은 사용 가능한 스레드가 생길 때까지 큐에 배치됩니다.

10. 고유한 스레드 풀을 구현할 수도 있지만 ThreadPool 클래스를 통해 .NET Framework에서 제공하는 스레드 풀을 사용하는 것이 더 간편합니다.

11. 스레드 풀링을 사용하는 경우 실행할 프로시저의 대리자를 사용하여 ThreadPool.QueueUserWorkItem 메서드를 호출하면 Visual Basic 또는 C#에서 스레드를 만들고 프로시저를 실행합니다.

<스레드 풀링 예제>

12. 다음 예제에서는 스레드 풀링를 사용하여 여러 가지 작업을 시작하는 방법을 보여 줍니다.

public void DoWork()
{
    // Queue a task.
    System.Threading.ThreadPool.QueueUserWorkItem(
        new System.Threading.WaitCallback(SomeLongTask));
    // Queue another task.
    System.Threading.ThreadPool.QueueUserWorkItem(
        new System.Threading.WaitCallback(AnotherLongTask));
}

private void SomeLongTask(Object state)
{
    // Insert code to perform a long task.
}

private void AnotherLongTask(Object state)
{
    // Insert code to perform a long task.
}

13. 스레드 풀링의 한 가지 장점(One advantage)은 상태 개체의 인수를 작업 프로시저에 전달할 수 있다는 점입니다.

14. 호출할 프로시저에 둘 이상의 인수가 필요한 경우 클래스의 인스턴스 또는 구조체를 Object 데이터 형식으로 캐스팅할 수 있습니다.

<스레드 풀 매개 변수 및 반환 값>

15. 스레드 풀 스레드에서 값을 반환하는 것은 간단하지 않습니다(straightforward).

16. Sub 프로시저가 스레드 풀의 큐에 들어갈 수 있는 유일한 프로시저 형식이므로 함수 호출에서 값을 반환하는 일반적인 방법은 사용할 수 없습니다.

17. 매개 변수를 제공하고 값을 반환할 수 있는 한 가지 방법은 다중 스레드 프로시저의 매개 변수 및 반환 값에 설명된대로 매개 변수, 반환 값 및 메서드를 래퍼 클래스에 래핑하는 것입니다.

18. QueueUserWorkItem 메서드의 선택적 ByVal 상태 개체 변수를 사용하면 보다 쉽게 매개 변수를 제공하고 값을 반환할 수 있습니다.

19. 이 변수를 사용하여 클래스의 인스턴스에 대한 참조를 전달하면 스레드 풀 스레드에서는 이 인스턴스의 멤버를 수정하게 되고 이 인스턴스 멤버가 반환 값으로 사용됩니다.

20. 처음에는(At first) 값으로 전달된 변수에서 참조하는 개체를 수정할 수 있다는 것이 이해가 되지 않을 수 있습니다.

21. 이는 개체 참조만이 값으로 전달되기 때문에 가능합니다.

22. 개체 참조에서 참조하는 개체의 멤버를 변경하면 변경 내용이 실제 클래스 인스턴스에 적용됩니다.

23. 상태 객체 내에서 값을 반환할 때는 구조체(Structures)를 사용할 수 없습니다.

24. 구조체는 값 형식이므로 비동기 프로세스에서 수행한 변경 내용이 원본 구조체의 멤버를 변경하지 않습니다.

25. 반환 값이 필요하지 않을 경우에 매개 변수를 제공하려면 구조체를 사용합니다.

방법: 스레드 풀 사용

1. 스레드 풀링은 작업이 큐에 추가된 다음 스레드가 만들어질 때 자동으로 시작되는 다중 스레딩의 한 형태입니다.

2. 자세한 내용은 스레드 풀링을 참조하십시오.

3. 다음 예제에서는 .NET Framework 스레드 풀을 사용하여 20과 40 사이의 숫자 10개(ten numbers between 20 and 40)에 대한 Fibonacci 결과를 계산합니다.

4. 각 Fibonacci 결과를 Fibonacci 클래스로 표현됩니다.

5. 이 클래스는 계산을 수행하는 ThreadPoolCallback이라는 메서드를 제공합니다.

6. 각 Fibonacci 값을 나타내는 개체가 작성되고 ThreadPoolCallback 메서드가 QueueUserWorkItem에 전달되면 이 메서드를 실행하기 위해 풀의 사용 가능한 스레드가 할당됩니다.

7. 각 Fibonacci 개체에는 계산을 위해 어느 정도 임의(semi-random)로 지정되는 값이 할당되고 각 스레드는 프로세서 시간을 할당 받기 위해 경쟁(compete)하므로 10개의 결과를 모두 계산하는 데 얼마나 오래 걸릴지 미리(in advance) 알 수 없습니다.

8. 생성 과정에서 각 Fibonacci 개체가 ManualResetEvent 클래스의 인스턴스에 전달되는 이유는 바로 여기에 있습니다.

9. 각 개체는 해당 계산이 완료되면 제공된 이벤트 개체에 신호를 보내므로(signal) 10개의 Fibonacci 개체가 모두 결과를 계산할 때까지 기본 스레드에서 WaitAll을 사용한 실행을 차단할 수 있습니다.

10. 그런 다음 Main 메서드에서 각 Fibonacci 결과를 표시합니다.

using System;
using System.Threading;

public class Fibonacci
{
    private int _n;
    private int _fibOfN;
    private ManualResetEvent _doneEvent;

    public int N { get { return _n; } }
    public int FibOfN { get { return _fibOfN; } }

    // Constructor.
    public Fibonacci(int n, ManualResetEvent doneEvent)
    {
        _n = n;
        _doneEvent = doneEvent;
    }

    // Wrapper method for use with thread pool.
    public void ThreadPoolCallback(Object threadContext)
    {
        int threadIndex = (int)threadContext;
        Console.WriteLine("thread {0} started...", threadIndex);
        _fibOfN = Calculate(_n);
        Console.WriteLine("thread {0} result calculated...", threadIndex);
        _doneEvent.Set();
    }

    // Recursive method that calculates the Nth Fibonacci number.
    public int Calculate(int n)
    {
        if (n <= 1)
        {
            return n;
        }

        return Calculate(n - 1) + Calculate(n - 2);
    }
}

public class ThreadPoolExample
{
    static void Main()
    {
        const int FibonacciCalculations = 10;

        // One event is used for each Fibonacci object.
        ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations];
        Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations];
        Random r = new Random();

        // Configure and start threads using ThreadPool.
        Console.WriteLine("launching {0} tasks...", FibonacciCalculations);
        for (int i = 0; i < FibonacciCalculations; i++)
        {
            doneEvents[i] = new ManualResetEvent(false);
            Fibonacci f = new Fibonacci(r.Next(20, 40), doneEvents[i]);
            fibArray[i] = f;
            ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
        }

        // Wait for all threads in pool to calculate.
        WaitHandle.WaitAll(doneEvents);
        Console.WriteLine("All calculations are complete.");

        // Display the results.
        for (int i= 0; i<FibonacciCalculations; i++)
        {
            Fibonacci f = fibArray[i];
            Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN);
        }
    }
}
launching 10 tasks...
thread 0 started...
thread 1 started...
thread 1 result calculated...
thread 2 started...
thread 2 result calculated...
thread 3 started...
thread 3 result calculated...
thread 4 started...
thread 0 result calculated...
thread 5 started...
thread 5 result calculated...
thread 6 started...
thread 4 result calculated...
thread 7 started...
thread 6 result calculated...
thread 8 started...
thread 8 result calculated...
thread 9 started...
thread 9 result calculated...
thread 7 result calculated...
All calculations are complete.
Fibonacci(38) = 39088169
Fibonacci(29) = 514229
Fibonacci(25) = 75025
Fibonacci(22) = 17711
Fibonacci(38) = 39088169
Fibonacci(29) = 514229
Fibonacci(29) = 514229
Fibonacci(38) = 39088169
Fibonacci(21) = 10946
Fibonacci(27) = 196418