Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Когда вы предоставляете задачу из 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. Дополнительные шаблоны для избегания в параллельных сценариях см. в разделе распространенные ошибки при использовании async/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
Не теряйте референсы во время сброса
Распространенная ошибка появляется в сбрасываемых асинхронных примитивах. Исправьте путь сброса путем атомарного переключения ссылок и завершения предыдущей задачи (например, с отменой):
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.