Что на самом деле происходит при использовании 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();

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

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

2 ответов


я рекомендую вам не думать об этом как "с помощью 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 is старт асинхронная операция для каждого элемента. Если вы хотите дождаться их завершения, используйте Task.WhenAll:

await Task.WhenAll(tasks);

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


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

конечно. С async и await внутри оператора 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.