Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Gdy uwidacznisz zadanie z TaskCompletionSource<TResult>, jesteś właścicielem jego okresu trwania. Wykonaj to zadanie na każdej ścieżce. Jeśli jakakolwiek ścieżka zostanie pominięta, wywołujący będą czekać w nieskończoność.
Ukończ każdą ścieżkę kodu
Zawsze wykonuj zadanie w ścieżkach sukcesu i porażki. Użyj bloku catch do logiki czyszczącej, gdy zadanie zakończy się niepowodzeniem. Użyj bloku finally do logiki czyszczenia, która musi być zawsze uruchamiana. Poniższy blok kodu przedstawia dodawanie czyszczenia dla ścieżki awarii.
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
Poniższy kod przechwytuje wyjątki, rejestruje je i zapomina o wywołaniu SetException lub TrySetException. Ta usterka pojawia się często i powoduje, że osoby wywołujące czekają bez końca. Aby uzyskać więcej informacji na temat obsługi wyjątków z zadaniami, zobacz Obsługa wyjątków zadań.
// ⚠️ 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
Preferuj TrySet* w rywalizacjach o ukończenie
Współbieżne ścieżki często ścigają się, aby ukończyć to samo TaskCompletionSource.
SetResult, SetExceptioni SetCanceled zgłaszaj, czy zadanie zostało już ukończone. W kodzie podatnym na wyścig użyj wartości TrySetResult, TrySetExceptioni TrySetCanceled. Aby dowiedzieć się więcej o wzorcach, których należy unikać w scenariuszach współbieżnych, zajrzyj do Typowe problemy z asynchronicznością/await.
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
Nie usuwaj odwołań podczas resetowania
Typowa usterka pojawia się w resetowalnych prymitywach asynchronicznych. Napraw ścieżkę resetu przez atomowe zamiany wskaźników i ukończenie poprzedniego zadania procesowego (z wykorzystaniem anulowania):
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
Nie rób tego: Jeśli zastąpisz wystąpienie przed ukończeniem TaskCompletionSource poprzedniego, kelnerzy, którzy przechowują stare zadanie, może nigdy nie zostać ukończone.
// ⚠️ 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
- Ukończ każde uwidocznione
TaskCompletionSourcezadanie na ścieżkach powodzenia, niepowodzenia i anulowania. - Użyj
TrySet*interfejsów API w ścieżkach, które mogą się ścigać. - Podczas resetowania, ukończ lub anuluj stare zadanie przed usunięciem odwołania.
- Dodaj testy oparte na czasie, aby szybko wykrywać i przerywać zawieszenia w ciągłej integracji.