Async ждет в linq select
Мне нужно изменить существующую программу и содержать следующий код:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Но это кажется мне очень странным, прежде всего, использование async
и await
в select. В соответствии с этим ответом Стивеном Клири я должен уметь их отбросить.
Затем второй Select
, который выбирает результат. Разве это не означает, что задача не является асинхронной, и выполняется синхронно (столько усилий ни для чего), или будет выполняться задача асинхронно, и когда она выполняется, остальная часть запроса выполняется?
Должен ли я написать вышеприведенный код следующим образом еще один ответ Стивена Клири:
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
и все ли это похоже?
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Пока я работаю над этим проектом, я бы хотел изменить первый пример кода, но я не слишком сильно хочу изменить (азартно работающий) асинхронный код. Может быть, я просто беспокоюсь ни о чем, и все 3 примера кода делают то же самое?
ProcessEventsAsync выглядит следующим образом:
async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
Ответы
Ответ 1
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Но это кажется мне очень странным, прежде всего, использование async и ожидание в выборе. В соответствии с этим ответом Стивена Клири я должен уметь их отбросить.
Вызов Select
действителен. Эти две линии по существу идентичны:
events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))
(Есть незначительная разница в том, как синхронное исключение будет выбрано из ProcessEventAsync
, но в контексте этого кода это вообще не имеет значения.)
Затем выбирается второй, который выбирает результат. Разве это не означает, что задача не является асинхронной, и выполняется синхронно (столько усилий ни для чего), или будет выполняться задача асинхронно, и когда она выполняется, остальная часть запроса выполняется?
Это означает, что запрос блокируется. Поэтому он не является асинхронным.
Разрушение:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
сначала начнет асинхронную операцию для каждого события. Тогда эта строка:
.Select(t => t.Result)
будет ждать завершения этих операций по одному (сначала он ждет первую операцию события, затем следующую, затем следующую и т.д.).
Это часть, на которую меня не волнует, потому что она блокирует и также переносит любые исключения из AggregateException
.
и все ли это похоже?
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Да, эти два примера эквивалентны. Они оба запускают все асинхронные операции (events.Select(...)
), затем асинхронно ждут завершения всех операций в любом порядке (await Task.WhenAll(...)
), затем продолжайте остаток работы (Where...
).
Оба этих примера отличаются от исходного кода. Исходный код блокирует и обертывает исключения в AggregateException
.
Ответ 2
Существующий код работает, но блокирует поток.
.Select(async ev => await ProcessEventAsync(ev))
создает новую задачу для каждого события, но
.Select(t => t.Result)
блокирует поток, ожидающий завершения каждой новой задачи.
С другой стороны, ваш код производит одинаковый результат, но сохраняет асинхронность.
Только один комментарий к вашему первому коду. Эта строка
var tasks = await Task.WhenAll(events...
создаст одну задачу, поэтому переменная должна быть названа в единственном числе.
Наконец, ваш последний код делает то же самое, но более краткий
Для справки: Task.Wait/Task.WhenAll
Ответ 3
С текущими методами, доступными в Linq, это выглядит довольно уродливо:
var tasks = items.Select(
async item => new
{
Item = item,
IsValid = await IsValid(item)
});
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
.Where(p => p.IsValid)
.Select(p => p.Item)
.ToList();
Надеемся, что следующие версии .NET придумают более элегантное инструментальное средство для обработки коллекций задач и задач коллекций.