День 1043. #ЗаметкиНаПолях #AsyncTips
Использование LINQ с асинхронными потоками
Создание асинхронных потоков
Потребление асинхронных потоков
Задача: обработать асинхронный поток с использованием чётко определенных и хорошо протестированных операторов. Либо использовать LINQ, когда предикат является асинхронным, например, необходимо провести поиск каждого элемента в базе данных или API, чтобы узнать, должен ли он быть включен в итоговую последовательность.
Решение
Допустим, у нас есть метод, производящий асинхронную последовательность:
async IAsyncEnumerable<int> GetAsyncRange()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
yield return i;
}
}
IAsyncEnumerable<T> включает поддержку LINQ в NuGet-пакете
System.Linq.Async. Заметьте, что при этом в коде нужно использовать
using static System.Linq.AsyncEnumerable;
Операторы LINQ можно применять к асинхронным потокам. Доступны все знакомые операторы:
Where,
Select,
SelectMany,
Join и т.д. Результат представляет собой асинхронный поток:
IAsyncEnumerable<int> values =
GetAsyncRange().Where(value => value % 2 == 0);
await foreach (int result in values) {
Console.WriteLine(result);
}
Но что, если нам нужно передать в
Where асинхронный предикат?
Where их не поддерживает. Но есть подходящий оператор
WhereAwait:
IAsyncEnumerable<int> values =
GetAsyncRange().WhereAwait(
async value => {
// Асинхронная работа для определения,
// должен ли элемент быть включён в результат
await Task.Delay(10);
return value % 2 == 0;
});
Методы LINQ для асинхронных потоков можно использовать и для обычных перечисляемых объектов, вызвав
ToAsyncEnumerable() для любого
IEnumerable<T>. Результат можно использовать с
WhereAwait,
SelectAwait и другими операторами, принимающими асинхронные делегаты.
Операторы LINQ для асинхронных потоков могут оканчиваться суффиксами
Async и
Await. Операторы, заканчивающиеся суффиксом
Async, возвращают обычное значение, допускающее ожидание. Операторы с суффиксом
Await получают асинхронный делегат. Т.е.
Await в имени подразумевает, что они используют
await внутри переданного делегата.
Пример
WhereAwait был приведёт выше.
Суффикс
Async применяется только к операторам терминации (termination operators), которые извлекают некоторое значение или выполняют некоторые вычисления и возвращают асинхронное скалярное значение. Пример такого оператора —
CountAsync, версия
Count для асинхронного потока, которая может подсчитать количество элементов, соответствующих некоторому предикату:
int count = await GetAsyncRange()
.CountAsync(value => value % 2 == 0);
Предикат может также быть асинхронным. В этом случае используется метод
CountAwaitAsync, поскольку он получает асинхронного делегата и производит одно терминальное значение:
int count = await GetAsyncRange().CountAwaitAsync(
async value => {
await Task.Delay(10);
return value % 2 == 0;
});
Итого
- Операторы без суффиксов (
Where,
Select) получают синхронные делегаты и выдают асинхронную последовательность.
- Операторы, которые получают асинхронные делегаты, имеют суффикс
Await (
WhereAwait,
SelectAwait), и выдают асинхронную последовательность.
- Операторы, возвращающие терминальное значение, имеют суффикс
Async (
CountAsync), а также могут иметь суффикс
Await, если получают асинхронный делегат (
CountAwaitAsync).
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 3.