Leer en inglés

Compartir a través de


CountdownEvent

System.Threading.CountdownEvent es un primitivo de sincronización que desbloquea los subprocesos de espera después de haber sido señalado un número de veces determinado. CountdownEvent está diseñado para escenarios en los que de lo contrario se tendría que usar ManualResetEvent o ManualResetEventSlim y disminuir manualmente una variable antes de señalar el evento. Por ejemplo, en un escenario de bifurcación/combinación, puede crear simplemente una clase CountdownEvent que tiene un recuento de señal de 5 y, a continuación, iniciar cinco elementos de trabajo del grupo de subprocesos y realizar cada llamada Signal del elemento de trabajo cuando se completa. Cada llamada a Signal disminuye el recuento de señales en 1. En el subproceso principal, la llamada a Wait se bloqueará hasta que el recuento de señales sea cero.

Nota

Para el código que no tiene que interactuar con las API de sincronización de .NET Framework heredadas, considere el uso de objetos System.Threading.Tasks.Task o del método Invoke para disponer de un método más sencillo para expresar el paralelismo bifurcación-combinación.

CountdownEvent tiene estas características adicionales:

  • La operación de espera puede cancelarse mediante el uso de tokens de cancelación.

  • Una vez creada la instancia, se puede incrementar su recuento de señales.

  • Las instancias se pueden reutilizar después de que se devuelva Wait tras una llamada al método Reset.

  • Las instancias exponen WaitHandle para la integración con otras API de sincronización de .NET como WaitAll.

Uso básico

En el siguiente ejemplo se muestra cómo se usa CountdownEvent con los elementos de trabajo ThreadPool.

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 con cancelación

En el ejemplo siguiente se muestra cómo cancelar la operación de espera en CountdownEvent utilizando un token de cancelación. El patrón básico sigue el modelo de cancelación unificada, que se presentó en .NET Framework 4. Para más información, consulte el tema sobre la cancelación en subprocesos administrados.

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

Tenga en cuenta que la operación de espera no cancela los subprocesos que la señalan. Normalmente, la cancelación se aplica a una operación lógica que puede incluir la espera del evento, así como todos los elementos de trabajo que la espera sincroniza. En este ejemplo, a cada elemento de trabajo se le pasa una copia del mismo token de cancelación, para que pueda responder a la solicitud de cancelación.

Vea también