Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Wenn Sie eine Aufgabe von TaskCompletionSource<TResult> aktivieren, besitzen Sie die Lebensdauer der Aufgabe. Führen Sie diese Aufgabe auf jedem Pfad aus. Wenn ein Pfad den Abschluss überspringt, warten Anrufer unendlich lange.
Vervollständigen Sie jeden Codepfad.
Führen Sie die Aufgabe immer in Erfolgs- und Fehlerpfaden aus. Verwenden Sie einen catch Block für die Bereinigungslogik, wenn die Aufgabe fehlschlägt. Verwenden Sie einen finally Block für die Bereinigungslogik, die immer ausgeführt werden muss. Der folgende Codeblock zeigt, wie man einen Aufräumprozess für einen Fehlerpfad hinzufügt.
public sealed class MissingSetExceptionFix
{
public Task<string> StartAsync(bool fail)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
try
{
if (fail)
{
throw new InvalidOperationException("Simulated failure");
}
tcs.TrySetResult("success");
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
}
}
Public NotInheritable Class MissingSetExceptionFix
Public Function StartAsync(fail As Boolean) As Task(Of String)
Dim tcs = New TaskCompletionSource(Of String)(TaskCreationOptions.RunContinuationsAsynchronously)
Try
If fail Then
Throw New InvalidOperationException("Simulated failure")
End If
tcs.TrySetResult("success")
Catch ex As Exception
tcs.TrySetException(ex)
End Try
Return tcs.Task
End Function
End Class
Der folgende Code fängt eine Ausnahme ab, protokolliert sie und vergisst, SetException oder TrySetException aufzurufen. Dieser Fehler tritt häufig auf und bewirkt, dass Anrufer für immer warten. Weitere Informationen zur Ausnahmebehandlung mit Aufgaben finden Sie unter Ausnahmebehandlung bei Aufgaben.
// ⚠️ DON'T copy this snippet. It demonstrates a problem that causes hangs.
public sealed class MissingSetExceptionBug
{
public Task<string> StartAsync(bool fail)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
try
{
if (fail)
{
throw new InvalidOperationException("Simulated failure");
}
tcs.SetResult("success");
}
catch (Exception)
{
// BUG: forgot SetException or TrySetException.
}
return tcs.Task;
}
}
' ⚠️ DON'T copy this snippet. It demonstrates a problem that causes hangs.
Public NotInheritable Class MissingSetExceptionBug
Public Function StartAsync(fail As Boolean) As Task(Of String)
Dim tcs = New TaskCompletionSource(Of String)(TaskCreationOptions.RunContinuationsAsynchronously)
Try
If fail Then
Throw New InvalidOperationException("Simulated failure")
End If
tcs.SetResult("success")
Catch ex As Exception
' BUG: forgot SetException or TrySetException.
End Try
Return tcs.Task
End Function
End Class
Vorziehen TrySet* bei Abschlussrennen
Gleichzeitige Pfade laufen oft an, um dasselbe TaskCompletionSourceabzuschließen.
SetResult, SetExceptionund SetCanceled auslösen, wenn die Aufgabe bereits abgeschlossen wurde. Verwenden Sie im rennanfälligen Code TrySetResult, TrySetException und TrySetCanceled. Weitere Muster, die in gleichzeitigen Szenarien vermieden werden sollen, finden Sie unter "Allgemeine asynchrone/await"-Fehler.
public static class TrySetRaceExample
{
public static void ShowRaceSafeCompletion()
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
bool first = tcs.TrySetResult(42);
bool second = tcs.TrySetException(new TimeoutException("Too late"));
Console.WriteLine($"First completion won: {first}");
Console.WriteLine($"Second completion accepted: {second}");
Console.WriteLine($"Result: {tcs.Task.Result}");
}
}
Public Module TrySetRaceExample
Public Sub ShowRaceSafeCompletion()
Dim tcs = New TaskCompletionSource(Of Integer)(TaskCreationOptions.RunContinuationsAsynchronously)
Dim first As Boolean = tcs.TrySetResult(42)
Dim second As Boolean = tcs.TrySetException(New TimeoutException("Too late"))
Console.WriteLine($"First completion won: {first}")
Console.WriteLine($"Second completion accepted: {second}")
Console.WriteLine($"Result: {tcs.Task.Result}")
End Sub
End Module
Verweise während der Zurücksetzung nicht löschen.
Ein häufiger Fehler tritt in zurücksetzbaren asynchronen Primitiven auf. Korrigieren Sie den Zurücksetzungspfad, indem Sie Referenzen atomar austauschen und die vorherige Aufgabe abschließen (zum Beispiel durch Abbrechen):
public sealed class ResetFix
{
private TaskCompletionSource<bool> _signal = NewSignal();
public Task WaitAsync() => _signal.Task;
public void Reset()
{
TaskCompletionSource<bool> previous = Interlocked.Exchange(ref _signal, NewSignal());
previous.TrySetCanceled();
}
public void Pulse()
{
_signal.TrySetResult(true);
}
private static TaskCompletionSource<bool> NewSignal() =>
new(TaskCreationOptions.RunContinuationsAsynchronously);
}
Public NotInheritable Class ResetFix
Private _signal As TaskCompletionSource(Of Boolean) = NewSignal()
Public Function WaitAsync() As Task
Return _signal.Task
End Function
Public Sub Reset()
Dim previous As TaskCompletionSource(Of Boolean) = Interlocked.Exchange(_signal, NewSignal())
previous.TrySetCanceled()
End Sub
Public Sub Pulse()
_signal.TrySetResult(True)
End Sub
Private Shared Function NewSignal() As TaskCompletionSource(Of Boolean)
Return New TaskCompletionSource(Of Boolean)(TaskCreationOptions.RunContinuationsAsynchronously)
End Function
End Class
Führen Sie dies nicht aus: Wenn Sie eine TaskCompletionSource Instanz austauschen, bevor Sie die vorherige Aufgabe beendet haben, werden die Wartevorgänge, die die alte Aufgabe enthalten, möglicherweise nie beendet.
// ⚠️ DON'T copy this snippet. It demonstrates a problem where old waiters never complete.
public sealed class ResetBug
{
private TaskCompletionSource<bool> _signal = NewSignal();
public Task WaitAsync() => _signal.Task;
public void Reset()
{
// BUG: waiters on the old task might never complete.
_signal = NewSignal();
}
public void Pulse()
{
_signal.TrySetResult(true);
}
private static TaskCompletionSource<bool> NewSignal() =>
new(TaskCreationOptions.RunContinuationsAsynchronously);
}
' ⚠️ DON'T copy this snippet. It demonstrates a problem where old waiters never complete.
Public NotInheritable Class ResetBug
Private _signal As TaskCompletionSource(Of Boolean) = NewSignal()
Public Function WaitAsync() As Task
Return _signal.Task
End Function
Public Sub Reset()
' BUG: waiters on the old task might never complete.
_signal = NewSignal()
End Sub
Public Sub Pulse()
_signal.TrySetResult(True)
End Sub
Private Shared Function NewSignal() As TaskCompletionSource(Of Boolean)
Return New TaskCompletionSource(Of Boolean)(TaskCreationOptions.RunContinuationsAsynchronously)
End Function
End Class
Checklist
- Führen Sie alle verfügbar gemachten
TaskCompletionSourceAufgaben auf Erfolgs-, Fehler- und Abbruchpfaden aus. - Verwenden Sie
TrySet*APIs in Pfaden, die zu einer Wettlaufsituation führen könnten. - Schließen Sie die alte Aufgabe während der Zurücksetzung ab, oder brechen Sie sie ab, bevor Sie den Verweis ablegen.
- Fügen Sie timeout-basierte Tests hinzu, damit Hänger im CI schnell fehlschlagen.