CountdownEvent

System.Threading.CountdownEvent 是同步處理原始物件,在收到特定次數的訊號後,該物件會解除對其等待執行緒的封鎖。 CountdownEvent 旨在用於以下案例:在傳送訊號至事件之前,您必須使用 ManualResetEventManualResetEventSlim 並需手動遞減變數。 例如,在 fork/join 案例中,您可以只建立具有 5 個訊號計數的 CountdownEvent,然後在執行緒集區上啟動五個工作項目,並讓每個工作項目在完成時呼叫 Signal。 每次呼叫 Signal 都會將訊號計數遞減 1。 在主執行緒上,直到訊號計數為零,都會封鎖對 Wait 的呼叫。

注意事項注意事項

若為無需與舊版 .NET Framework 同步化 API 互動的程式碼,請考慮使用 System.Threading.Tasks.Task 物件和 (或) ParallelInvoke() 方法,以更輕鬆的方式表示 fork-join 平行處理原則。

CountdownEvent 具有下列其他功能:

  • 使用取消語彙基元,可以取消等待作業。

  • 在建立執行個體之後,可以遞增其訊號計數。

  • 在透過呼叫 Reset 方法傳回 Wait 之後,可以重複使用執行個體。

  • 執行個體會公開 WaitHandle,以便與其他 .NET Framework 同步化 API 整合,例如 WaitAll

基本使用方式

下列範例示範如何使用 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