Отмена. Часть 6: Связывание. Окончание
Начало
Продолжение
Не используйте OperationCanceledException.CancellationToken
Рассмотрим снова исходный пример:
async Task DoAsync(
CancellationToken ct)
{
using var cts =
CancellationTokenSource
.CreateLinkedTokenSource(ct);
var task = DoOtherAsync(cts.Token);
… // Что-то делаем
… // возможно вызываем cts.Cancel()
await task;
}
Предположим, код вызывает DoAsync и реагирует на отмену:
async Task MainAsync()
{
using var cts =
new CancellationTokenSource();
cts.CancelAfter(2000);
try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
// ПЛОХОЙ КОД!!!
when (ex.CancellationToken == cts.Token)
{
Console.WriteLine("Timeout!");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
Замысел кода — сделать что-то особенное, если код отменяется из-за этого конкретного источника отмены. К сожалению, этот код проблематичен в реальном мире; DoAsync может использовать связанный источник токена отмены, в этом случае OperationCanceledException.CancellationToken не будет соответствовать cts.Token, даже если он был источником отмены!
Поэтому не используйте OperationCanceledException.CancellationToken. Правильное решение — проверять, запросил ли этот источник отмену:
async Task MainAsync()
{
…
catch (OperationCanceledException)
when (cts.IsCancellationRequested)
{
Console.WriteLine("Timeout!");
}
…
}
То же касается и проверки связанных источников отмены:
async Task DoAsync(
CancellationToken ct)
{
using var cts =
CancellationTokenSource
.CreateLinkedTokenSource(ct);
cts.CancelAfter(1000);
try
{
await DoOtherAsync(cts.Token);
}
catch (OperationCanceledException ex)
// ПЛОХОЙ КОД!!!
when (ex.CancellationToken == cts.Token)
{
… // делаем что-то при таймауте
throw;
}
}
С этим кодом та же проблема! Возможно, что DoAsync может сам использовать связанный токен отмены (или будет использовать в будущем). Решение — не использовать OperationCanceledException.CancellationToken:
async Task DoOtherAsync(
CancellationToken ct)
{
…
catch (OperationCanceledException ex)
when (cts.IsCancellationRequested)
{
… // делаем что-то при таймауте
throw;
}
}
Итого
В большинстве случаев не приходится использовать связанные токены отмены, но они бывают полезны. Вот, что следует запомнить:
1. Освобождайте (dispose) источники токенов отмены, включая связанные источники токенов отмены.
2. Не используйте OperationCanceledException.CancellationToken; вместо этого используйте IsCancellationRequested.
3. Для любого кода, который имеет несколько токенов в области действия, будьте внимательны к тому, на какой из них вы реагируете.
Источник: https://blog.stephencleary.com/2024/10/cancellation-6-linking.html