Export (0) Print
Expand All

CountdownEvent

System.Threading.CountdownEvent is a synchronization primitive that unblocks its waiting threads after it has been signaled a certain number of times. CountdownEvent is designed for scenarios in which you would otherwise have to use a ManualResetEvent or ManualResetEventSlim and manually decrement a variable before signaling the event. For example, in a fork/join scenario, you can just create a CountdownEvent that has a signal count of 5, and then start five work items on the thread pool and have each work item call Signal when it completes. Each call to Signal decrements the signal count by 1. On the main thread, the call to Wait will block until the signal count is zero.

Note Note

For code that does not have to interact with legacy .NET Framework synchronization APIs, consider using System.Threading.Tasks.Task objects or the Invoke method for an even easier approach to expressing fork-join parallelism.

CountdownEvent has these additional features:

  • The wait operation can be canceled by using cancellation tokens.

  • Its signal count can be incremented after the instance is created.

  • Instances can be reused after Wait has returned by calling the Reset method.

  • Instances expose a WaitHandle for integration with other .NET Framework synchronization APIs such as WaitAll.

The following example demonstrates how to use a CountdownEvent with ThreadPool work items.

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();
}
// .,.

The following example shows how to cancel the wait operation on CountdownEvent by using a cancellation token. The basic pattern follows the model for unified cancellation, which is introduced in .NET Framework 4. For more information, see Cancellation in Managed Threads.

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

Note that the wait operation does not cancel the threads that are signaling it. Typically, cancellation is applied to a logical operation, and that can include waiting on the event as well as all the work items that the wait is synchronizing. In this example, each work item is passed a copy of the same cancellation token so that it can respond to the cancellation request.

Show:
© 2014 Microsoft