Подтвердить что ты не робот

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) {...}
4b9b3361

Ответ 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 придумают более элегантное инструментальное средство для обработки коллекций задач и задач коллекций.