Erledigen Sie Ihre Aufgaben

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 TaskCompletionSource Aufgaben 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.

Siehe auch