Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Ibland behöver du avbrytning, men operationen som du väntar på accepterar inte en CancellationToken. I så fall väljer du det beteende du behöver:
- Avbryt själva åtgärden.
- Avbryt bara din väntan.
- Avbryt både åtgärden och din väntan.
Rätt val beror på vem som äger åtgärden och vilka rensningsgarantier du behöver.
Förstå de tre betydelserna för annullering
När folk säger "avbryt det här asynkrona samtalet" betyder de vanligtvis en av tre olika saker:
- Avbryt åtgärden. Signalera att åtgärden stoppar arbetet.
- Avbryt väntan. Sluta vänta och fortsätt med ditt arbetsflöde, även om processen fortfarande körs.
- Avbryt båda. Avbryt begäran och sluta vänta omedelbart.
Behandla dessa betydelser som separata designbeslut. Om du blandar dem blir avbokningsbeteendet svårt att resonera kring.
Föredrar tokenmedvetna API:er när de är tillgängliga
Innan du lägger till en omslutning kontrollerar du om API:et redan stöder annulleringstoken. Moderna .NET API:er har mycket bredare tokenstöd än äldre .NET Framework-kod. Till exempel stöder många stream-API:er i .NET nu avbrytning, och NetworkStream asynkrona åtgärder respekterar avbrytningstoken.
Använd tokenöverlagringar när de finns:
public static class StreamExamples
{
public static async Task<int> ReadOnceAsync(
NetworkStream stream,
byte[] buffer,
CancellationToken cancellationToken)
{
return await stream.ReadAsync(
buffer.AsMemory(0, buffer.Length),
cancellationToken);
}
}
Public Module StreamExamples
Public Async Function ReadOnceAsync(
stream As NetworkStream,
buffer As Byte(),
cancellationToken As CancellationToken) As Task(Of Integer)
Return Await stream.ReadAsync(
buffer.AsMemory(0, buffer.Length),
cancellationToken)
End Function
End Module
Avbryt bara väntan med hjälp av Task.WhenAny
När en åtgärd inte accepterar en token avbryter du din väntan genom att köra åtgärden mot en tokenstödd uppgift. Det här mönstret visas ofta som ett WithCancellation hjälpverktyg.
public static class TaskCancellationExtensions
{
public static async Task<T> WithCancellation<T>(
this Task<T> task,
CancellationToken cancellationToken)
{
if (task.IsCompleted)
return await task.ConfigureAwait(false);
var cancellationTaskSource = new TaskCompletionSource<bool>(
TaskCreationOptions.RunContinuationsAsynchronously);
using var registration = cancellationToken.Register(
static state =>
((TaskCompletionSource<bool>)state!).TrySetResult(true),
cancellationTaskSource);
Task completed = await Task.WhenAny(task, cancellationTaskSource.Task)
.ConfigureAwait(false);
if (completed != task)
throw new OperationCanceledException(cancellationToken);
return await task.ConfigureAwait(false);
}
}
Public Module TaskCancellationExtensions
<Extension()>
Public Async Function WithCancellation(Of T)(
operationTask As Task(Of T),
cancellationToken As CancellationToken) As Task(Of T)
If operationTask.IsCompleted Then
Return Await operationTask
End If
Dim cancellationTaskSource =
New TaskCompletionSource(Of Boolean)(TaskCreationOptions.RunContinuationsAsynchronously)
Using registration = cancellationToken.Register(
Sub(state)
DirectCast(state, TaskCompletionSource(Of Boolean)).TrySetResult(True)
End Sub,
cancellationTaskSource)
Dim completed = Await Task.WhenAny(operationTask, cancellationTaskSource.Task)
If completed IsNot operationTask Then
Throw New OperationCanceledException(cancellationToken)
End If
End Using
Return Await operationTask
End Function
End Module
Det här mönstret används WhenAny för att returnera så snart någon av uppgifterna har slutförts.
Använd endast den här metoden när det är säkert för den ursprungliga åtgärden att fortsätta i bakgrunden.
Avbryt både åtgärden och vänta när du äger åtgärden
Om du äger operationen och den accepterar en token, skicka token och använd fortfarande en avbrytbar väntan när det behövs.
public static class CancelBothDemo
{
public static async Task<string> FetchDataAsync(CancellationToken cancellationToken)
{
await Task.Delay(500, cancellationToken);
return "payload";
}
public static async Task RunAsync()
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(100);
try
{
string payload = await FetchDataAsync(cts.Token)
.WithCancellation(cts.Token);
Console.WriteLine(payload);
}
catch (OperationCanceledException)
{
Console.WriteLine("Canceled operation and wait.");
}
}
}
Public Module CancelBothDemo
Public Async Function FetchDataAsync(cancellationToken As CancellationToken) As Task(Of String)
Await Task.Delay(500, cancellationToken)
Return "payload"
End Function
Public Async Function RunAsync() As Task
Using cts = New CancellationTokenSource()
cts.CancelAfter(100)
Try
Dim payload = Await FetchDataAsync(cts.Token).WithCancellation(cts.Token)
Console.WriteLine(payload)
Catch ex As OperationCanceledException
Console.WriteLine("Canceled operation and wait.")
End Try
End Using
End Function
End Module
Den här kombinationen ger responsiv annullering för uppringare och kooperativ avstängning för det underliggande arbetet.
Hantera övergivna åtgärder på ett säkert sätt
Om du bara avbryter väntan kan den ursprungliga uppgiften senare misslyckas. Behåll en referens så att du kan observera slutförande- och loggfel. Annars kan du missa fel och göra felsökningen svårare.
public static class ObserveLateFaultDemo
{
private static async Task<int> FaultLaterAsync()
{
await Task.Delay(250);
throw new InvalidOperationException("Background operation failed.");
}
public static async Task RunAsync()
{
Task<int> operation = FaultLaterAsync();
using var cts = new CancellationTokenSource(50);
try
{
await operation.WithCancellation(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Stopped waiting; operation still running.");
}
_ = operation.ContinueWith(
t => Console.WriteLine($"Observed late fault: {t.Exception!.InnerException!.Message}"),
TaskContinuationOptions.OnlyOnFaulted);
await Task.Delay(300);
}
}
Public Module ObserveLateFaultDemo
Private Async Function FaultLaterAsync() As Task(Of Integer)
Await Task.Delay(250)
Throw New InvalidOperationException("Background operation failed.")
End Function
Public Async Function RunAsync() As Task
Dim operation As Task(Of Integer) = FaultLaterAsync()
Using cts = New CancellationTokenSource(50)
Try
Await operation.WithCancellation(cts.Token)
Catch ex As OperationCanceledException
Console.WriteLine("Stopped waiting; operation still running.")
End Try
End Using
Dim observed = operation.ContinueWith(
Sub(t)
Console.WriteLine($"Observed late fault: {t.Exception.InnerException.Message}")
End Sub,
TaskContinuationOptions.OnlyOnFaulted)
Await observed
End Function
End Module