Vorgehensweise: Lauschen auf mehrere Abbruchanforderungen
In diesem Beispiel wird gezeigt, wie Sie zwei Abbruchtoken gleichzeitig abhören können, um einen Vorgang abzubrechen, wenn dies von einem der beiden Token angefordert wird.
Hinweis
Wenn „Nur eigenen Code“ aktiviert ist, unterbricht Visual Studio die Ausführung in einigen Fällen in der Zeile, die die Ausnahme auslöst, und eine Fehlermeldung zu einer „nicht vom Benutzercode behandelten Ausnahme“ wird angezeigt. Dies ist ein gutartiger Fehler. Sie können F5 drücken, um den Vorgang fortzusetzen. In diesem Fall wird das in den nachstehenden Beispielen veranschaulichte Ausnahmebehandlungsverhalten angewendet. Um zu verhindern, dass Visual Studio beim ersten Fehler abbricht, deaktivieren Sie einfach unter Extras, Optionen, Debugging, Allgemein das Kontrollkästchen „Nur eigenen Code“.
Beispiel
Im folgenden Beispiel werden mithilfe der CreateLinkedTokenSource-Methode zwei Token zu einem Token verbunden. Dadurch kann das Token an Methoden übergeben werden, die nur ein Abbruchtoken als Argument akzeptieren. Das Beispiel veranschaulicht ein gängiges Szenario, in dem eine Methode sowohl ein Token, das von außerhalb der Klasse eingegangen ist, und ein Token, das innerhalb der Klasse generiert wurde, beachten muss.
using System;
using System.Threading;
using System.Threading.Tasks;
class LinkedTokenSourceDemo
{
static void Main()
{
WorkerWithTimer worker = new WorkerWithTimer();
CancellationTokenSource cts = new CancellationTokenSource();
// Task for UI thread, so we can call Task.Wait wait on the main thread.
Task.Run(() =>
{
Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.");
Console.WriteLine("Or let the task time out by doing nothing.");
if (Console.ReadKey(true).KeyChar == 'c')
cts.Cancel();
});
// Let the user read the UI message.
Thread.Sleep(1000);
// Start the worker task.
Task task = Task.Run(() => worker.DoWork(cts.Token), cts.Token);
try
{
task.Wait(cts.Token);
}
catch (OperationCanceledException e)
{
if (e.CancellationToken == cts.Token)
Console.WriteLine("Canceled from UI thread throwing OCE.");
}
catch (AggregateException ae)
{
Console.WriteLine("AggregateException caught: " + ae.InnerException);
foreach (var inner in ae.InnerExceptions)
{
Console.WriteLine(inner.Message + inner.Source);
}
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
cts.Dispose();
}
}
class WorkerWithTimer
{
CancellationTokenSource internalTokenSource = new CancellationTokenSource();
CancellationToken internalToken;
CancellationToken externalToken;
Timer timer;
public WorkerWithTimer()
{
// A toy cancellation trigger that times out after 3 seconds
// if the user does not press 'c'.
timer = new Timer(new TimerCallback(CancelAfterTimeout), null, 3000, 3000);
}
public void DoWork(CancellationToken externalToken)
{
// Create a new token that combines the internal and external tokens.
this.internalToken = internalTokenSource.Token;
this.externalToken = externalToken;
using (CancellationTokenSource linkedCts =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
{
try
{
DoWorkInternal(linkedCts.Token);
}
catch (OperationCanceledException)
{
if (internalToken.IsCancellationRequested)
{
Console.WriteLine("Operation timed out.");
}
else if (externalToken.IsCancellationRequested)
{
Console.WriteLine("Cancelling per user request.");
externalToken.ThrowIfCancellationRequested();
}
}
}
}
private void DoWorkInternal(CancellationToken token)
{
for (int i = 0; i < 1000; i++)
{
if (token.IsCancellationRequested)
{
// We need to dispose the timer if cancellation
// was requested by the external token.
timer.Dispose();
// Throw the exception.
token.ThrowIfCancellationRequested();
}
// Simulating work.
Thread.SpinWait(7500000);
Console.Write("working... ");
}
}
public void CancelAfterTimeout(object? state)
{
Console.WriteLine("\r\nTimer fired.");
internalTokenSource.Cancel();
timer.Dispose();
}
}
Imports System.Threading
Imports System.Threading.Tasks
Class LinkedTokenSourceDemo
Shared Sub Main13()
Dim worker As New WorkerWithTimer()
Dim cts As New CancellationTokenSource()
' Task for UI thread, so we can call Task.Wait wait on the main thread.
Task.Run(Sub()
Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.")
Console.WriteLine("Or let the task time out by doing nothing.")
If Console.ReadKey(True).KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)
' Let the user read the UI message.
Thread.Sleep(1000)
' Start the worker task.
Dim t As Task = Task.Run(Sub() worker.DoWork(cts.Token), cts.Token)
Try
t.Wait()
Catch ae As AggregateException
For Each inner In ae.InnerExceptions
Console.WriteLine(inner.Message)
Next
End Try
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
cts.Dispose()
End Sub
End Class
Class WorkerWithTimer
Dim internalTokenSource As CancellationTokenSource
Dim token As CancellationToken
Dim timer As Timer
Public Sub New()
internalTokenSource = New CancellationTokenSource()
token = internalTokenSource.Token
' A toy cancellation trigger that times out after 3 seconds
' if the user does not press 'c'.
timer = New Timer(New TimerCallback(AddressOf CancelAfterTimeout), Nothing, 3000, 3000)
End Sub
Public Sub DoWork(ByVal externalToken As CancellationToken)
' Create a new token that combines the internal and external tokens.
Dim internalToken As CancellationToken = internalTokenSource.Token
Dim linkedCts As CancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
Using (linkedCts)
Try
DoWorkInternal(linkedCts.Token)
Catch e As OperationCanceledException
If e.CancellationToken = internalToken Then
Console.WriteLine("Operation timed out.")
ElseIf e.CancellationToken = externalToken Then
Console.WriteLine("Canceled by external token.")
externalToken.ThrowIfCancellationRequested()
End If
End Try
End Using
End Sub
Private Sub DoWorkInternal(ByVal token As CancellationToken)
For i As Integer = 0 To 1000
If token.IsCancellationRequested Then
' We need to dispose the timer if cancellation
' was requested by the external token.
timer.Dispose()
' Output for demonstration purposes.
Console.WriteLine(vbCrLf + "Cancelling per request.")
' Throw the exception.
token.ThrowIfCancellationRequested()
End If
' Simulating work.
Thread.SpinWait(7500000)
Console.Write("working... ")
Next
End Sub
Public Sub CancelAfterTimeout(ByVal state As Object)
Console.WriteLine(vbCrLf + "Timer fired.")
internalTokenSource.Cancel()
timer.Dispose()
End Sub
End Class
Wenn das verknüpfte Token eine OperationCanceledException auslöst, ist das Token, das an die Ausnahme übergeben wird, das verknüpfte Token, und nicht eines der Vorgängertoken. Um zu ermitteln, welches Token abgebrochen wurde, prüfen Sie direkt den Status der Vorgängertoken.
In diesem Beispiel sollte AggregateException niemals ausgelöst werden, aber es wird hier dennoch abgefangen, da in der Praxis alle anderen Ausnahmen außer OperationCanceledException, die von dem Aufgabendelegat ausgelöst werden, in AggregateException eingeschlossen sind.