A feladatok elvégzése

Amikor kibocsát egy tevékenységet a TaskCompletionSource<TResult>-ből, Ön lesz a tevékenység élettartamának tulajdonosa. Végezze el ezt a feladatot minden útvonalon. Ha bármelyik folyamat nem fejeződik be, a hívók örökké várakoznak.

Minden kódútvonal befejezése

A feladatot mindig sikeres és sikertelen útvonalakon végezze el. A feladat meghiúsulása esetén használjon blokkot catch a tisztítási logikához. Használjon olyan blokkot finally a törlési logikához, amelyet mindig futtatnia kell. Az alábbi kódblokk egy hibás útvonal tisztításának hozzáadását mutatja be.

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

Az alábbi kód kivételt kap, naplózza, és elfelejt meghívni SetException vagy TrySetException. Ez a hiba gyakran jelenik meg, és a hívók örökké várakoznak. A tevékenységek kivételkezelésével kapcsolatos további részletekért lásd: Tevékenységkivételek kezelése.

// ⚠️ 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

Előnyben részesítés TrySet* a befejező versenyeken

Az egyidejű útvonalak gyakran versenyeznek, hogy ugyanazt TaskCompletionSource teljesítsék. SetResult, SetExceptionmajd SetCanceled dobjon, ha a feladat már befejeződött. A versenyfeltételezett kódban használja a TrySetResult, TrySetException és TrySetCanceled. A konkurens helyzetekben elkerülendő mintákról további információkért lásd: Common async/await bugs.

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

Ne vesse el a hivatkozásokat az alaphelyzetbe állítás során

Gyakori hiba jelenik meg a visszaállítható aszinkron primitívekben. Javítsa ki az alaphelyzetbe állítási útvonalat a hivatkozások atomi felcserélésével és az előző feladat elvégzésével (például lemondással):

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

Ne tegye a következőt: Ha az előző végrehajtása előtt lecserél egy TaskCompletionSource példányt, előfordulhat, hogy a régi feladatot tartalmazó várakozók sosem fejezik be a feladatukat.

// ⚠️ 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

  • Végezze el az összes feltárt TaskCompletionSource feladatot a sikeres, sikertelen és megszakítási útvonalakon.
  • Használjon TrySet* API-kat olyan útvonalakon, amelyek versenyben lehetnek.
  • Az alaphelyzetbe állítás során fejezze be vagy szakítsa meg a régi feladatot, mielőtt elveti a referenciát.
  • Időtúllépés-alapú tesztek hozzáadása a gyorsabb hibadetektálás érdekében a CI folyamán fellépő lefagyások esetén.

Lásd még