當你從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
以下程式碼會捕捉一個例外,記錄下來,並忘記呼叫 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, , SetException, SetCanceled 如果任務已完成則拋出。 在競態條件程式碼中,使用 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 中快速失敗。