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

Что на самом деле происходит при использовании async/await внутри оператора LINQ?

Следующий фрагмент компиляции, но я ожидаю, что он ждет результат задачи вместо того, чтобы дать мне List<Task<T>>.

var foo = bars.Select(async bar => await Baz(bar)).ToList()

Как указано здесь, вам нужно использовать Task.WhenAll:

var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
await Task.WhenAll(tasks);

Но комментарий указывает, что async и await внутри Select() не нужны:

var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();

Аналогичный вопрос здесь, где кто-то пытается использовать метод async внутри Where().

Итак, async и await внутри оператора LINQ - это легальный синтаксис, но делает он вообще ничего или не использует его?

4b9b3361

Ответ 1

Я рекомендую вам не думать об этом как "используя async в LINQ". Имейте в виду, что между ними: делегаты. Несколько операторов LINQ принимают делегатов, а async можно использовать для создания асинхронного делегата.

Итак, когда у вас есть асинхронный метод BazAsync, который возвращает Task:

Task BazAsync(TBar bar);

то этот код приводит к последовательности задач:

IEnumerable<Task> tasks = bars.Select(bar => BazAsync(bar));

Аналогично, если вы используете async и await внутри делегата, вы создаете асинхронный делегат, который возвращает Task:

IEnumerable<Task> tasks = bars.Select(async bar => await BazAsync(bar));

Эти два выражения LINQ функционально эквивалентны. Нет важных отличий.

Как и обычные выражения LINQ, IEnumerable<Task> оценивается по лени. Только с помощью асинхронных методов, таких как BazAsync, вам обычно не нужна случайная двойная оценка или что-то в этом роде. Итак, когда вы проецируете последовательность задач, обычно рекомендуется сразу же подтвердить последовательность. Это вызывает BazAsync для всех элементов исходной последовательности, начиная все выполняемые задачи:

Task[] tasks = bars.Select(bar => BazAsync(bar)).ToArray();

Конечно, все, что мы сделали с помощью Select, запускает асинхронную операцию для каждого элемента. Если вы хотите дождаться их завершения, используйте Task.WhenAll:

await Task.WhenAll(tasks);

Большинство других операторов LINQ работают не так просто с асинхронными делегатами. Select довольно просто: вы только начинаете асинхронную операцию для каждого элемента.

Ответ 2

имеет ли он определенное использование

Конечно. С помощью async и ожидания внутри оператора LINQ вы можете, например, сделайте что-нибудь вроде этого:

var tasks = foos.Select( async foo =>
    {
        var intermediate =  await DoSomethingAsync( foo );
        return await DoSomethingElseAsync( intermediate );
    } ).ToList();
await Task.WhenAll(tasks);

Без async/await внутри оператора LINQ вы ничего не ожидаете в инструкции LINQ, поэтому вы не можете обработать результат или ждать чего-то еще.

Без async/await в операторе LINQ вы запускаете только задачи, но не дожидаетесь их завершения. В конце концов они по-прежнему будут завершены, но это произойдет задолго до того, как элемент управления оставит оператор LINQ, поэтому вы сможете получить доступ к их результатам только после завершения строки WhenAll, но не внутри оператора LINQ.