Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Когда вы предоставляете задачу из 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.