Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

5 лет назад
Открыть в
День 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.