Auf Englisch lesen

Freigeben über


CountdownEvent

System.Threading.CountdownEvent ist eine Synchronisierungsprimitive, die die Blockierung ihrer wartenden Threads nach einer bestimmten Zahl an sie gerichteter Signalisierungen aufhebt. CountdownEvent eignet sich für Szenarien, in denen Sie andernfalls ein ManualResetEvent oder ManualResetEventSlim verwenden und manuell eine Variable vor dem Signalisieren des Ereignisses verringern müssen. In einem Fork/Join-Szenario können Sie z.B. nur ein CountdownEvent mit einer Signalanzahl von 5 erstellen und dann fünf Arbeitselemente im Threadpool starten und jedes Arbeitselement bei Abschluss Signal aufrufen lassen. Jeder Aufruf von Signal reduziert die Signalanzahl um 1. Im Hauptthread wird der Aufruf von Wait blockiert, bis die Signalanzahl 0 (null) ist.

Hinweis

Erwägen Sie für Code, der nicht für die Interaktion mit älteren .NET Framework-Synchronisierungs-APIs vorgesehen ist, einen noch einfacheren Ansatz zum Ausdrücken der Fork/Join-Parallelität mit Verwendung von System.Threading.Tasks.Task-Objekten oder der Invoke-Methode.

CountdownEvent besitzt diese zusätzlichen Features:

  • Der Wait-Vorgang kann mithilfe von Abbruchtoken abgebrochen werden.

  • Die Signalanzahl kann nach dem Erstellen der Instanz erhöht werden.

  • Instanzen können wiederverwendet werden, nachdem Wait zur Rückgabe die Reset-Methode aufgerufen hat.

  • Instanzen machen ein WaitHandle für die Integration mit anderen .NET-Synchronisierungs-APIs wie WaitAll verfügbar.

Grundlegende Verwendung

Im folgenden Beispiel wird die Verwendung eines CountdownEvent mit ThreadPool-Arbeitselementen veranschaulicht.

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 mit Abbruch

Im folgenden Beispiel wird der Abbruch des Wartevorgangs für CountdownEvent durch Verwenden eines Abbruchtokens beschrieben. Das grundlegende Muster folgt dem in .NET Framework 4 eingeführten Modell für einheitlichen Abbruch. Weitere Informationen finden Sie unter Abbruch in verwalteten Threads.

class CancelableCountdownEvent
{
    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!
            }
        }
        finally {
            e.Dispose();
            cts.Dispose();
        }
        //...
    } //end method
} //end class

Beachten Sie, dass der Wartevorgang nicht die Threads abbricht, die ihn signalisieren. In der Regel wird der Abbruch auf eine logische Operation angewendet, und das kann sowohl das Warten auf das Ereignis als auch alle Arbeitselemente beinhalten, die der Wartevorgang synchronisiert. In diesem Beispiel wird jedem Arbeitselement eine Kopie desselben Abbruchtokens übergeben, sodass es auf die Abbruchanforderung reagieren kann.

Weitere Informationen