작업 완료

작업을 TaskCompletionSource<TResult>에 공개할 때, 작업의 수명을 관리하게 됩니다. 모든 경로에서 해당 작업을 완료합니다. 경로가 완료되지 않을 경우, 호출자는 영원히 기다립니다.

모든 코드 경로 완료

항상 성공 및 실패 경로에서 작업을 완료합니다. 작업이 실패할 때 catch 블록을 정리 논리에 사용합니다. 항상 실행해야 하는 정리 논리를 위해 finally 블록을 사용하세요. 다음 코드 블록은 실패 경로에 대한 정리 추가를 보여 줍니다.

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

다음 코드는 예외를 catch한 다음 기록하며, SetException 또는 TrySetException을 호출하지 않습니다. 이 버그는 자주 나타나고 호출자가 영원히 기다리게 합니다. 태스크를 사용한 예외 처리에 대한 자세한 내용은 작업 예외 처리를 참조하세요.

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

완료 경쟁에서 TrySet*를 선호합니다

동시 경로는 동일한 작업을 완료하기 위해 경합하는 TaskCompletionSource경우가 많습니다. SetResult, SetExceptionSetCanceled 태스크가 이미 완료된 경우 throw합니다. 경합이 발생하기 쉬운 코드에서 TrySetResult, TrySetException, 및 TrySetCanceled를 사용하십시오. 동시 시나리오에서 피해야 할 더 많은 패턴은 일반적인 비동기/대기 버그를 참조하세요.

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

다시 설정하는 동안 참조 삭제 안 함

초기화 가능한 비동기 기본 형식에 일반적인 버그가 나타납니다. 참조를 원자성으로 교환하고 이전 작업(예: 취소)을 완료하여 재설정 경로를 수정합니다.

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

하지 마십시오: 이전 작업을 완료하기 전에 TaskCompletionSource 인스턴스를 대체하면, 이전 작업을 담당하고 있는 대기 중인 프로세스가 완료되지 않을 수 있습니다.

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

  • 성공, 실패 및 취소 경로에 대해 노출된 TaskCompletionSource 모든 작업을 완료합니다.
  • 경쟁 상태가 발생할 수 있는 경로에서는 TrySet* API를 사용하십시오.
  • 다시 설정하는 동안 참조를 삭제하기 전에 이전 작업을 완료하거나 취소합니다.
  • CI에서 중단이 빠르게 실패할 수 있도록 시간 제한 기반 테스트를 추가합니다.

참고하십시오