День 1050. #ЗаметкиНаПолях #AsyncTips
Асинхронные потоки и отмена
Создание асинхронных потоков
Потребление асинхронных потоков
Использование LINQ с асинхронными потоками
Задача: требуется механизм отмены асинхронных потоков.
Решение
Не всем асинхронным потокам необходима отмена. Перечисление может быть просто остановлено при достижении условия. Если это единственная разновидность «отмены», реально необходимая в программе, то полноценная отмена не нужна, как показывает следующий пример (см. код метода GetAsyncRange здесь):
await foreach (int result in GetAsyncRange())
{
Console.WriteLine(result);
if (result >= 8)
break;
}
Часто бывает полезно отменять сами асинхронные потоки, передав маркер отмены потоку-источнику. Async метод, возвращающий IAsyncEnumerable<T>, может получать маркер отмены, который должен быть помечен атрибутом EnumeratorCancellation. После этого маркер можно использовать естественным образом:
async IAsyncEnumerable<int> GetAsyncRange(
[EnumeratorCancellation] CancellationToken ct = default)
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100, ct);
yield return i;
}
}
using var cts = new CancellationTokenSource(500);
CancellationToken token = cts.Token;
await foreach (int result in GetAsyncRange(token)) {
Console.WriteLine(result);
}
В этом примере CancellationToken передается непосредственно методу, возвращающему асинхронный перечислитель. Это самый распространенный вариант использования.
Возможны и другие сценарии, когда маркер отмены применяется к перечислителю. Это нетипичный, но важный сценарий использования. По этой причине асинхронные потоки поддерживают метод расширения WithCancellation, который может использоваться для присоединения маркера отмены к конкретной итерации асинхронного потока:
async Task ConsumeSequence(IAsyncEnumerable<int> items)
{
using var cts = new CancellationTokenSource(500);
CancellationToken ct = cts.Token;
await foreach (int item
in items.WithCancellation(ct))
{
Console.WriteLine(item);
}
}
await ConsumeSequence(GetAsyncRange());
Примечания:
1. Заметьте, что GetAsyncRange() вызывается без токена отмены. При наличии атрибута EnumeratorCancellation компилятор автоматически обеспечит передачу маркера из WithCancellation параметру метода GetAsyncRange, помеченному атрибутом EnumeratorCancellation.
2. В нынешнем виде отменённое перечисление выбрасывает TaskCanceledException, которое нужно обработать в клиентском коде.
3. Метод расширения WithCancellation не препятствует ConfigureAwait(false). Оба метода расширения могут быть объединены в цепочку:
… items.WithCancellation(ct).ConfigureAwait(false)) …
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 3.