Partager via


CountdownEvent

System.Threading.CountdownEvent est une primitive de synchronisation qui débloque ses threads en attente après qu'il a été signalé un certain nombre de fois. CountdownEvent est conçu pour les scénarios dans lesquels vous utiliseriez plutôt un ManualResetEvent ou ManualResetEventSlim et décrémenteriez manuellement une variable avant de signaler l'événement. Par exemple, vous pouvez juste créer un CountdownEvent qui a un nombre de signal de 5 dans un scénario de bifurcation/jointure, puis démarrer cinq éléments de travail sur le pool de threads et faire en sorte que chaque élément de travail appelle Signal lorsqu'il est terminé. Chaque appel à Signal décrémente le nombre de signal par 1. Sur le thread principal, l'appel à Wait se bloquera jusqu'à ce que le nombre de signal soit nul.

RemarqueRemarque

Pour le code qui n'a pas à interagir avec les API de synchronisation .NET Framework héritées, envisagez d'utiliser des objets System.Threading.Tasks.Task et/ou la méthode ParallelInvoke() pour une même approche encore plus facile permettant d'exprimer le parallélisme de bifurcation-jointure.

CountdownEvent comprend ces fonctionnalités supplémentaires :

  • L'opération d'attente peut être annulée à l'aide des jetons d'annulation.

  • Son nombre de signal peut être incrémenté après que l'instance a été créée.

  • Les instances peuvent être réutilisées après que Wait a été retourné en appelant la méthode Reset.

  • Les instances exposent un WaitHandle pour l'intégration avec d'autres API de synchronisation .NET Framework telles que WaitAll.

Utilisation de base

L'exemple suivant montre comment utiliser un CountdownEvent avec des éléments de travail 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 avec annulation

L'exemple suivant indique comment annuler l'opération d'attente sur CountdownEvent à l'aide d'un jeton d'annulation. Le modèle de base suit le modèle pour l'annulation unifiée, présentée dans le .NET Framework version 4. Pour plus d'informations, consultez Annulation.

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

Notez que l'opération d'attente n'annule pas les threads qui le signalent. En général, l'annulation est appliquée à une opération logique, et qui peut inclure l'attente sur l'événement ainsi que tous les éléments de travail que l'attente synchronise. Dans cet exemple, une copie du même jeton d'annulation est passée à chaque élément de travail afin qu'il puisse répondre à la demande d'annulation.

Voir aussi

Autres ressources

EventWaitHandle, AutoResetEvent, CountdownEvent et ManualResetEvent