Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
System.Threading.CountdownEvent est une primitive de synchronisation qui débloque ses threads d’attente après avoir été signalé un certain nombre de fois. CountdownEvent est conçu pour les scénarios dans lesquels vous devrez autrement utiliser une ManualResetEvent variable ou ManualResetEventSlim décrémenter manuellement une variable avant de signaler l’événement. Par exemple, dans un scénario de duplication/jointure, vous pouvez simplement créer un CountdownEvent signal dont le nombre de signaux est égal à 5, puis démarrer cinq éléments de travail sur le pool de threads et avoir chaque appel Signal d’élément de travail lorsqu’il se termine. Chaque appel à Signal décrémente le nombre de signaux de 1. Sur le thread principal, l’appel à bloquera jusqu’à ce que le nombre de signaux soit égal à Wait zéro.
Remarque
Pour le code qui n’a pas besoin d’interagir avec les API de synchronisation .NET Framework héritées, envisagez d’utiliser des System.Threading.Tasks.Task objets ou la Invoke méthode pour une approche encore plus simple pour exprimer le parallélisme de jointure de fourche.
CountdownEvent dispose de ces fonctionnalités supplémentaires :
L’opération d’attente peut être annulée à l’aide de jetons d’annulation.
Son nombre de signaux peut être incrémenté après la création de l’instance.
Les instances peuvent être réutilisées une fois Wait retournées en appelant la Reset méthode.
Les instances exposent une WaitHandle intégration à d’autres API de synchronisation .NET, telles que WaitAll.
Utilisation de base
L’exemple suivant montre comment utiliser un CountdownEvent avec ThreadPool des éléments de travail.
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();
}
// .,.
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()
Compte à rebours avec annulation
L’exemple suivant montre comment annuler l’opération d’attente à CountdownEvent l’aide d’un jeton d’annulation. Le modèle de base suit le modèle d’annulation unifiée, qui a été introduit dans .NET Framework 4. Pour plus d’informations, consultez Annulation dans les threads managés.
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 {dataWithToken.Data.Num}");
return;
}
for (int i = 0; i < 10000; i++)
{
if (dataWithToken.Token.IsCancellationRequested)
{
Console.WriteLine($"Cancelling while executing {dataWithToken.Data.Num}");
return;
}
// Increase this value to slow down the program.
Thread.SpinWait(100000);
}
Console.WriteLine($"Processed {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
Option Strict On
Option Explicit On
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
Finally
e.Dispose()
cts.Dispose()
End Try
End Sub
End Class
End Module
Notez que l’opération d’attente n’annule pas les threads qui le signalent. En règle générale, l’annulation est appliquée à une opération logique et peut inclure l’attente sur l’événement, ainsi que tous les éléments de travail que l’attente synchronise. Dans cet exemple, chaque élément de travail est passé une copie du même jeton d’annulation afin qu’il puisse répondre à la demande d’annulation.