CountdownEvent
System.Threading.CountdownEvent는 특정 횟수만큼 신호를 받은 후에 대기 중인 스레드를 차단 해제하는 동기화 기본 형식입니다. CountdownEvent는 ManualResetEvent 또는 ManualResetEventSlim을 사용해야 하고 이벤트에 신호를 보내기 전에 수동으로 변수를 감소시켜야 하는 시나리오에서 대안으로 사용하도록 설계되어 있습니다. 예를 들어, 분기/조인 시나리오에서 신호 카운트가 5인 CountdownEvent를 만든 다음 스레드 풀의 작업 항목 5개를 시작하고 각 작업 항목이 완료될 때 작업 항목에서 Signal을 호출하도록 하면 됩니다. Signal를 호출할 때마다 신호 카운트가 1씩 감소합니다. 주 스레드에서 Wait에 대한 호출은 신호 카운트가 0이 될 때까지 차단됩니다.
참고 |
---|
레거시 .NET Framework 동기화 API와 상호 작용할 필요가 없는 코드의 경우 분기/조인 병렬 처리를 훨씬 더 쉽게 표현하기 위하여 System.Threading.Tasks.Task 개체 및/또는 ParallelInvoke() 메서드를 사용하는 것이 좋습니다. |
CountdownEvent에는 다음과 같은 추가 기능이 있습니다.
대기 작업은 취소 토큰을 사용하여 취소할 수 있습니다.
신호 카운트는 인스턴스가 만들어진 후 증가할 수 있습니다.
인스턴스는 다른 .NET Framework 동기화 API와의 통합을 위해 WaitAll과 같은 WaitHandle을 노출합니다.
기본 사용법
다음 예제에서는 CountdownEvent를 ThreadPool 작업 항목과 함께 사용하는 방법을 보여 줍니다.
Dim source As IEnumerable(Of Data) = GetData()
Dim e = New CountdownEvent(1)
' Fork work:
For Each element As Data In source
' Dynamically increment signal count.
e.AddCount()
ThreadPool.QueueUserWorkItem(Sub(state)
Try
ProcessData(state)
Finally
e.Signal()
End Try
End Sub,
element)
Next
' Decrement the signal count by the one we added
' in the constructor.
e.Signal()
' The first element could also be run on this thread.
' ProcessData(New Data(0))
' Join with work:
e.Wait()
IEnumerable<Data> source = GetData();
using (CountdownEvent e = new CountdownEvent(1))
{
// fork work:
foreach (Data element in source)
{
// Dynamically increment signal count.
e.AddCount();
ThreadPool.QueueUserWorkItem(delegate(object state)
{
try
{
ProcessData(state);
}
finally
{
e.Signal();
}
},
element);
}
e.Signal();
// The first element could be run on this thread.
// Join with work.
e.Wait();
}
// .,.
CountdownEvent 취소
다음 예제에서는 취소 토큰을 사용하여 CountdownEvent에 대한 대기 작업을 취소하는 방법을 보여 줍니다. 기본 패턴은 .NET Framework 버전 4에서 도입된 통합 취소 모델을 따릅니다. 자세한 내용은 취소를 참조하십시오.
Option Strict On
Option Explicit On
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
Module CancelEventWait
Class Data
Public Num As Integer
Public Sub New(ByVal i As Integer)
Num = i
End Sub
Public Sub New()
End Sub
End Class
Class DataWithToken
Public Token As CancellationToken
Public _data As Data
Public Sub New(ByVal d As Data, ByVal ct As CancellationToken)
Me._data = d
Me.Token = ct
End Sub
End Class
Class Program
Shared Function GetData() As IEnumerable(Of Data)
Dim nums = New List(Of Data)
For i As Integer = 1 To 5
nums.Add(New Data(i))
Next
Return nums
End Function
Shared Sub ProcessData(ByVal obj As Object)
Dim dataItem As DataWithToken = CType(obj, DataWithToken)
If dataItem.Token.IsCancellationRequested = True Then
Console.WriteLine("Canceled before starting {0}", dataItem._data.Num)
Exit Sub
End If
' Increase this value to slow down the program.
For i As Integer = 0 To 10000
If dataItem.Token.IsCancellationRequested = True Then
Console.WriteLine("Cancelling while executing {0}", dataItem._data.Num)
Exit Sub
End If
Thread.SpinWait(100000)
Next
Console.WriteLine("Processed {0}", dataItem._data.Num)
End Sub
Shared Sub Main()
DoEventWithCancel()
Console.WriteLine("Press the enter key to exit.")
Console.ReadLine()
End Sub
Shared Sub DoEventWithCancel()
Dim source As IEnumerable(Of Data) = GetData()
Dim cts As CancellationTokenSource = New CancellationTokenSource()
' Enable cancellation request from a simple UI thread.
Task.Factory.StartNew(Sub()
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)
' Must have a count of at least 1 or else it is signaled.
Dim e As CountdownEvent = New CountdownEvent(1)
For Each element As Data In source
Dim item As DataWithToken = New DataWithToken(element, cts.Token)
' Dynamically increment signal count.
e.AddCount()
ThreadPool.QueueUserWorkItem(Sub(state)
ProcessData(state)
If cts.Token.IsCancellationRequested = False Then
e.Signal()
End If
End Sub,
item)
Next
' Decrement the signal count by the one we added
' in the constructor.
e.Signal()
' The first element could be run on this thread.
' ProcessData(source(0))
' Join with work or catch cancellation exception
Try
e.Wait(cts.Token)
Catch ex As OperationCanceledException
If ex.CancellationToken = cts.Token Then
Console.WriteLine("User canceled.")
Else : Throw ' we don't know who canceled us.
End If
End Try
End Sub
End Class
End Module
class CancelableCountdowEvent
{
class Data
{
public int Num { get; set; }
public Data(int i) { Num = i; }
public Data() { }
}
class DataWithToken
{
public CancellationToken Token { get; set; }
public Data Data { get; private set; }
public DataWithToken(Data data, CancellationToken ct)
{
this.Data = data;
this.Token = ct;
}
}
static IEnumerable<Data> GetData()
{
return new List<Data>() { new Data(1), new Data(2), new Data(3), new Data(4), new Data(5) };
}
static void ProcessData(object obj)
{
DataWithToken dataWithToken = (DataWithToken)obj;
if (dataWithToken.Token.IsCancellationRequested)
{
Console.WriteLine("Canceled before starting {0}", dataWithToken.Data.Num);
return;
}
for (int i = 0; i < 10000; i++)
{
if (dataWithToken.Token.IsCancellationRequested)
{
Console.WriteLine("Cancelling while executing {0}", dataWithToken.Data.Num);
return;
}
// Increase this value to slow down the program.
Thread.SpinWait(100000);
}
Console.WriteLine("Processed {0}", dataWithToken.Data.Num);
}
static void Main(string[] args)
{
EventWithCancel();
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
static void EventWithCancel()
{
IEnumerable<Data> source = GetData();
CancellationTokenSource cts = new CancellationTokenSource();
//Enable cancellation request from a simple UI thread.
Task.Factory.StartNew(() =>
{
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
});
// Event must have a count of at least 1
CountdownEvent e = new CountdownEvent(1);
// fork work:
foreach (Data element in source)
{
DataWithToken item = new DataWithToken(element, cts.Token);
// Dynamically increment signal count.
e.AddCount();
ThreadPool.QueueUserWorkItem(delegate(object state)
{
ProcessData(state);
if (!cts.Token.IsCancellationRequested)
e.Signal();
},
item);
}
// Decrement the signal count by the one we added
// in the constructor.
e.Signal();
// The first element could be run on this thread.
// Join with work or catch cancellation.
try
{
e.Wait(cts.Token);
}
catch (OperationCanceledException oce)
{
if (oce.CancellationToken == cts.Token)
{
Console.WriteLine("User canceled.");
}
else throw; //We don't know who canceled us!
}
e.Dispose();
//...
} //end method
} //end class
대기 작업에 신호를 보내는 스레드는 해당 대기 작업에 의해 취소되지 않습니다. 일반적으로 취소는 논리적 작업에 적용되고, 이러한 작업에는 이벤트에 대한 대기 및 대기를 통해 동기화되고 있는 모든 작업 항목이 포함될 수 있습니다. 이 예제에서는 동일한 취소 토큰의 복사본이 각 작업 항목에 전달되므로 해당 작업 항목에서 취소 요청에 응답할 수 있습니다.
참고 항목
기타 리소스
EventWaitHandle, AutoResetEvent, CountdownEvent 및 ManualResetEvent