CountdownEvent
System.Threading.CountdownEvent è una primitiva di sincronizzazione che sblocca i thread in attesa dopo essere stata segnalata un determinato numero di volte. CountdownEvent è progettato per scenari nei quali si dovrebbe altrimenti utilizzare un oggetto ManualResetEvent o ManualResetEventSlim e diminuire manualmente una variabile prima di segnalare l'evento. Ad esempio, in uno scenario di divisione/unione, è possibile creare un oggetto CountdownEvent con un conteggio segnali pari a 5, quindi avviare cinque elementi di lavoro nel pool di thread e fare in modo che ogni elemento di lavoro chiami Signal quando viene completato. Ogni chiamata a Signal diminuisce di 1 il conteggio segnali. Nel thread principale, la chiamata a Wait si bloccherà fino a che il conteggio segnali non sarà zero.
Nota |
---|
Nel caso di codice che non deve interagire con le API di sincronizzazione di .NET Framework legacy, considerare l'utilizzo di oggetti System.Threading.Tasks.Task e/o del metodo ParallelInvoke() per un approccio ancora più semplice all'espressione del parallelismo di divisione-unione. |
CountdownEvent presenta le seguenti funzionalità aggiuntive:
L'operazione di attesa può essere annullata tramite token di annullamento.
Il conteggio segnali può essere incrementato dopo la creazione dell'istanza.
Una volta che Wait è stato restituito mediante la chiamata al metodo Reset, le istanze possono essere riutilizzate.
Le istanze espongono un oggetto WaitHandle per l'integrazione con altre API di sincronizzazione di .NET Framework, ad esempio WaitAll.
Utilizzo di base
Nell'esempio seguente viene illustrato come utilizzare un oggetto CountdownEvent con elementi di lavoro 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 con annullamento
Nell'esempio seguente viene illustrato come annullare l'operazione di attesa su CountdownEvent tramite un token di annullamento. Il modello di base segue il modello dell'annullamento unificato, introdotto in .NET Framework versione 4. Per ulteriori informazioni, vedere Annullamento.
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
L'operazione di attesa non annulla i thread di segnalazione. In genere, l'annullamento è applicato a un'operazione logica e ciò può includere l'attesa nell'evento e tutti gli elementi di lavoro che l'attesa sta sincronizzando. In questo esempio, a ogni elemento di lavoro viene passata una copia dello stesso token di annullamento in modo che possa rispondere alla richiesta di annullamento.
Vedere anche
Altre risorse
EventWaitHandle, AutoResetEvent, CountdownEvent e ManualResetEvent