관리되는 스레드 풀
업데이트: 2007년 11월
ThreadPool 클래스를 사용하면 시스템에서 관리하는 작업자 스레드 풀이 응용 프로그램에 제공되므로 사용자는 스레드 관리가 아닌 응용 프로그램 작업에 집중할 수 있습니다. 백그라운드 프로세스가 필요한 짧은 작업이 있는 경우 관리되는 스레드 풀을 사용하여 다중 스레드를 활용할 수 있습니다.
참고: |
---|
.NET Framework 버전 2.0 서비스 팩 1부터는 이전 릴리스의 .NET Framework에서 병목 현상이 발생하는 것으로 파악된 세 개의 주요 영역인 작업을 큐에 대기시키기, 스레드 풀 스레드 디스패치 및 I/O 완료 스레드 디스패치에서 스레드 풀의 처리량이 크게 향상되었습니다. 이 기능을 사용하려면 응용 프로그램이 .NET Framework 버전 3.5를 대상으로 해야 합니다. 자세한 내용은 .NET Framework 3.5 아키텍처를 참조하십시오. |
사용자 인터페이스와 상호 작용하는 백그라운드 작업의 경우 .NET Framework 버전 2.0에서는 사용자 인터페이스 스레드에서 발생되는 이벤트를 사용하여 통신하는 BackgroundWorker 클래스도 제공합니다.
.NET Framework에서는 비동기 I/O 완료, 타이머 콜백, 등록된 대기 작업, 대리자를 사용한 비동기 메서드 호출 및 System.Net 소켓 연결을 포함한 여러 가지 목적으로 스레드 풀 스레드를 사용합니다.
스레드 풀 스레드를 사용하지 않는 경우
스레드 풀 스레드를 사용하는 대신 사용자 고유의 스레드를 만들고 관리하는 것이 적합한 몇 가지 시나리오는 다음과 같습니다.
포그라운드 스레드가 필요한 경우
스레드에 특정 우선 순위를 지정해야 하는 경우
스레드를 오랫동안 차단하는 작업이 있는 경우 스레드 풀에 최대 스레드 수가 있으므로 다수의 차단된 스레드 풀 스레드가 작업의 시작을 방해할 수 있습니다.
단일 스레드 아파트에 스레드를 배치해야 하는 경우 모든 ThreadPool 스레드는 다중 스레드 아파트에 있습니다.
스레드에 연결된 안정적인 ID가 있어야 하거나 하나의 스레드를 하나의 작업에 전용으로 사용해야 하는 경우
스레드 풀 특징
스레드 풀 스레드는 백그라운드 스레드입니다. 포그라운드 및 백그라운드 스레드를 참조하십시오. 각 스레드는 기본 스택 크기를 사용하고 기본 우선 순위에 따라 실행되며 다중 스레드 아파트에 있습니다.
프로세스당 스레드 풀이 하나만 있습니다.
스레드 풀 스레드의 예외 사항
스레드 풀 스레드의 처리되지 않은 예외로 인해 프로세스가 종료됩니다. 다음은 이 규칙의 세 가지 예외 사항입니다.
Abort가 취소되었으므로 스레드 풀 스레드에서 ThreadAbortException이 throw됩니다.
응용 프로그램 도메인이 언로드 중이므로 스레드 풀 스레드에서 AppDomainUnloadedException이 throw됩니다.
공용 언어 런타임 또는 호스트 프로세스에서 스레드를 종료합니다.
자세한 내용은 관리되는 스레드의 예외를 참조하십시오.
참고: |
---|
.NET Framework 버전 1.0 및 1.1에서 공용 언어 런타임은 스레드 풀 스레드에서 처리되지 않은 예외를 포착합니다. 이렇게 하면 응용 프로그램 상태가 손상되어 결국 응용 프로그램이 중단될 수 있고, 이에 따라 디버깅이 어려워질 수 있습니다. |
최대 스레드 풀 스레드 수
스레드 풀의 큐에 들어갈 수 있는 작업 수는 사용할 수 있는 메모리에 의해서만 제한됩니다. 그러나 스레드 풀이 동시에 프로세스에서 활성화될 수 있는 스레드 수를 제한합니다. 기본적으로 제한되는 수는 CPU당 25 작업자 스레드 및 1,000 I/O 완료 스레드입니다.
GetMaxThreads 및 SetMaxThreads 메서드를 사용하여 최대 스레드 수를 제어할 수 있습니다.
참고: |
---|
.NET Framework 버전 1.0 및 1.1에서는 관리 코드에서 스레드 풀의 크기를 설정할 수 없습니다. 공용 언어 런타임을 호스팅하는 코드는 mscoree.h에 정의되어 있는 CorSetMaxThreads를 사용하여 크기를 설정할 수 있습니다. |
최소 유휴 스레드 수
스레드 풀은 모든 스레드가 유휴 상태인 경우에도 사용 가능한 최소 스레드 수를 유지하므로 대기 중인 작업을 즉시 시작할 수 있습니다. 이 최소 수를 초과하는 유휴 스레드는 시스템 리소스 저장을 위해 종료됩니다. 기본적으로 프로세서당 하나의 유휴 스레드를 유지합니다.
스레드 풀에는 새 유휴 스레드가 시작되기 전에 기본으로 제공되는 지연 시간(.NET Framework 버전 2.0의 경우 0.5초)이 있습니다. 응용 프로그램에서 주기적으로 짧은 시간에 많은 작업을 시작하는 경우 유휴 스레드 수를 조금 늘려도 처리량이 크게 증가할 수 있습니다. 유휴 스레드 수를 너무 높게 설정하면 시스템 리소스가 불필요하게 사용됩니다.
GetMinThreads 및 SetMinThreads 메서드를 사용하여 스레드 풀에서 유지하는 유휴 스레드 수를 제어할 수 있습니다.
참고: |
---|
.NET Framework 버전 1.0에서는 최소 유휴 스레드 수를 설정할 수 없습니다. |
보안 검사 건너뛰기
스레드 풀은 ThreadPool.UnsafeQueueUserWorkItem 및 ThreadPool.UnsafeRegisterWaitForSingleObject메서드를 제공합니다. 대기 중인 작업을 실행하는 동안 수행된 보안 검사와 호출자의 스택이 무관한 경우에만 이러한 메서드를 사용하십시오. QueueUserWorkItem및 RegisterWaitForSingleObject에서 모두 호출자의 스택을 캡처하며, 이것은 스레드에서 작업 실행을 시작하면 스레드 풀 스레드의 스택에 병합됩니다. 보안 검사가 필요하면 전체 스택을 검사해야 하는데, 안전성은 제공되지만 성능이 저하됩니다.
스레드 풀 사용
관리 코드에서 ThreadPool.QueueUserWorkItem을 호출하거나 비관리 코드에서 CorQueueUserWorkItem을 호출하고 작업을 수행하는 메서드를 나타내는 WaitCallback 대리자를 전달하여 스레드 풀을 사용합니다. ThreadPool.RegisterWaitForSingleObject 메서드를 사용하거나, 신호를 받거나 시간이 초과되었을 때 WaitOrTimerCallback 대리자에서 나타내는 메서드를 호출하는 WaitHandle을 전달하여 대기 작업에 관련된 작업 항목을 대기시킬 수도 있습니다. 두 가지 경우 모두 스레드 풀은 콜백 메서드를 호출하는 백그라운드 스레드를 사용합니다.
ThreadPool 예제
다음의 세 가지 코드 예제에서는 QueueUserWorkItem 및 RegisterWaitForSingleObject 메서드를 보여 줍니다.
첫 번째 예제에서는 QueueUserWorkItem 메서드를 사용하여 ThreadProc 메서드로 표시되는 아주 간단한 작업을 대기시킵니다.
Imports System
Imports System.Threading
Public Class Example
Public Shared Sub Main()
' Queue the task.
ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc))
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the thread pool task runs. The thread pool uses background
' threads, which do not keep the application running. (This
' is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
End Sub
' This thread procedure performs the task.
Shared Sub ThreadProc(stateInfo As Object)
' No state object was passed to QueueUserWorkItem, so
' stateInfo is null.
Console.WriteLine("Hello from the thread pool.")
End Sub
End Class
using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
QueueUserWorkItem에 작업 데이터 제공
다음 코드 예제에서는 QueueUserWorkItem 메서드를 사용하여 작업을 대기시키고 작업 데이터를 제공합니다.
Imports System
Imports System.Threading
' TaskInfo holds state information for a task that will be
' executed by a ThreadPool thread.
Public Class TaskInfo
' State information for the task. These members
' can be implemented as read-only properties, read/write
' properties with validation, and so on, as required.
Public Boilerplate As String
Public Value As Integer
' Public constructor provides an easy way to supply all
' the information needed for the task.
Public Sub New(text As String, number As Integer)
Boilerplate = text
Value = number
End Sub
End Class
Public Class Example
Public Shared Sub Main()
' Create an object containing the information needed
' for the task.
Dim ti As New TaskInfo("This report displays the number {0}.", 42)
' Queue the task and data.
If ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc), ti) Then
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the ThreadPool task has a chance to run. ThreadPool uses
' background threads, which do not keep the application
' running. (This is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
Else
Console.WriteLine("Unable to queue ThreadPool request.")
End If
End Sub
' The thread procedure performs the independent task, in this case
' formatting and printing a very simple report.
'
Shared Sub ThreadProc(stateInfo As Object)
Dim ti As TaskInfo = CType(stateInfo, TaskInfo)
Console.WriteLine(ti.Boilerplate, ti.Value)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
// State information for the task. These members
// can be implemented as read-only properties, read/write
// properties with validation, and so on, as required.
public string Boilerplate;
public int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
public TaskInfo(string text, int number) {
Boilerplate = text;
Value = number;
}
}
public class Example {
public static void Main() {
// Create an object containing the information needed
// for the task.
TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the ThreadPool task has a chance to run. ThreadPool uses
// background threads, which do not keep the application
// running. (This is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
else {
Console.WriteLine("Unable to queue ThreadPool request.");
}
}
// The thread procedure performs the independent task, in this case
// formatting and printing a very simple report.
//
static void ThreadProc(Object stateInfo) {
TaskInfo ti = (TaskInfo) stateInfo;
Console.WriteLine(ti.Boilerplate, ti.Value);
}
}
RegisterWaitForSingleObject
다음 예제에서는 몇 가지 스레딩 기능을 보여 줍니다.
RegisterWaitForSingleObject 메서드를 사용하여 ThreadPool 스레드에서 실행하는 작업을 대기시킵니다.
AutoResetEvent를 사용하여 작업에 실행 신호를 보냅니다. EventWaitHandle, AutoResetEvent 및 ManualResetEvent를 참조하십시오.
WaitOrTimerCallback 대리자를 사용하여 시간 제한 및 신호를 모두 처리합니다.
RegisteredWaitHandle을 사용하여 대기된 작업을 취소합니다.
Imports System
Imports System.Threading
' TaskInfo contains data that will be passed to the callback
' method.
Public Class TaskInfo
public Handle As RegisteredWaitHandle = Nothing
public OtherInfo As String = "default"
End Class
Public Class Example
Public Shared Sub Main()
' The main thread uses AutoResetEvent to signal the
' registered wait handle, which executes the callback
' method.
Dim ev As New AutoResetEvent(false)
Dim ti As New TaskInfo()
ti.OtherInfo = "First task"
' The TaskInfo for the task includes the registered wait
' handle returned by RegisterWaitForSingleObject. This
' allows the wait to be terminated when the object has
' been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject( _
ev, _
New WaitOrTimerCallback(AddressOf WaitProc), _
ti, _
1000, _
false _
)
' The main thread waits about three seconds, to demonstrate
' the time-outs on the queued task, and then signals.
Thread.Sleep(3100)
Console.WriteLine("Main thread signals.")
ev.Set()
' The main thread sleeps, which should give the callback
' method time to execute. If you comment out this line, the
' program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000)
' If you start a thread yourself, you can wait for it to end
' by calling Thread.Join. This option is not available with
' thread pool threads.
End Sub
' The callback method executes when the registered wait times out,
' or when the WaitHandle (in this case AutoResetEvent) is signaled.
' WaitProc unregisters the WaitHandle the first time the event is
' signaled.
Public Shared Sub WaitProc(state As Object, timedOut As Boolean)
' The state object must be cast to the correct type, because the
' signature of the WaitOrTimerCallback delegate specifies type
' Object.
Dim ti As TaskInfo = CType(state, TaskInfo)
Dim cause As String = "TIMED OUT"
If Not timedOut Then
cause = "SIGNALED"
' If the callback method executes because the WaitHandle is
' signaled, stop future execution of the callback method
' by unregistering the WaitHandle.
If Not ti.Handle Is Nothing Then
ti.Handle.Unregister(Nothing)
End If
End If
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", _
ti.OtherInfo, _
Thread.CurrentThread.GetHashCode().ToString(), _
cause _
)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public class Example {
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
1000,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}