如何:接聽多個取消要求
此範例顯示如何同時接聽兩個取消權杖,讓您可以在其中一個權杖要求作業時將作業取消。
注意
啟用 [Just My Code] 時,Visual Studio 在某些情況下會在擲回例外狀況的字行上中斷,並顯示錯誤訊息,指出「使用者程式碼未處理例外狀況」。這個錯誤是良性的。 您可以按 F5 鍵繼續,並查看下面範例中示範的例外狀況處理行為。 若要防止 Visual Studio 在遇到第一個錯誤時就中斷,只要取消核取 [工具]、[選項]、[偵錯]、[一般] 下的 [Just My Code] 核取方塊即可。
範例
在下列範例中,CreateLinkedTokenSource 方法是用來將兩個權杖聯結為一個權杖。 這可將權杖傳遞給僅採取一個取消權杖作為引數的方法。 此範例示範一個常見的案例,其中方法必須同時觀察從類別外部傳入的權杖,以及類別內部產生的權杖。
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
當連結的權杖擲回 OperationCanceledException 時,傳遞給例外狀況的權杖是連結的權杖,而非其中一個前置項權杖。 若要判斷哪一個權杖已取消,請直接檢查前置項權杖的狀態。
在此範例中,AggregateException 應該永遠不會擲回,但在真實案例中,除了從工作委派擲回 OperationCanceledException 之外的任何其他例外狀況會在 AggregateException 中換行,所以這時攔截到例外狀況程式碼。