Як чекати перелік завдань асинхронно за допомогою LINQ?


87

У мене є список завдань, які я створив таким чином:

public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
    var foos = await GetFoosAsync();

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

    ...
}

Завдяки використанню .ToList(), всі завдання повинні починатися. Тепер я хочу дочекатися їх завершення та повернути результати.

Це працює у наведеному вище ...блоці:

var list = new List<Foo>();
foreach (var task in tasks)
    list.Add(await task);
return list;

Він робить те, що я хочу, але це здається досить незграбним. Краще я б написав щось простіше, як це:

return tasks.Select(async task => await task).ToList();

... але це не компілюється. Чого мені не вистачає? Або просто не можливо висловити речі таким чином?


Вам потрібно обробляти DoSomethingAsync(foo)послідовно для кожного foo, або це кандидат для Parallel.ForEach <Foo> ?
mdisibio

1
@mdisibio - Parallel.ForEachблокує. Візерунок тут походить з асинхронного відео C # Джона Скіта на Pluralsight . Він виконується паралельно, не блокуючи.
Matt Johnson-Pint

@mdisibio - Ні. Вони бігають паралельно. Спробуйте . (До того ж, схоже, мені не потрібно, .ToList()якщо я просто буду використовувати WhenAll.)
Метт Джонсон-Пінт

Очко взяте. Залежно від того, як DoSomethingAsyncнаписано, список може виконуватися або не виконуватися паралельно. Я зміг написати тестовий метод, який був, а версію - ні, але в будь-якому випадку поведінка диктується самим методом, а не делегатом, що створює завдання. Вибачте за змішання. Однак, якщо DoSomethingAsycповертається Task<Foo>, тоді awaitделегат не є абсолютно необхідним ... Я думаю, це було головне, що я збирався спробувати зробити.
mdisibio

Відповіді:


137

LINQ не працює ідеально з asyncкодом, але ви можете зробити це:

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

Якщо всі ваші завдання повертають однаковий тип значення, тоді ви навіть можете зробити це:

var results = await Task.WhenAll(tasks);

що досить приємно. WhenAllповертає масив, тому я вважаю, що ваш метод може повернути результати безпосередньо:

return await Task.WhenAll(tasks);

11
Просто хотів зазначити, що це також може працювати зvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
або навітьvar tasks = foos.Select(DoSomethingAsync).ToList();
Тодд Меньє

3
в чому причина того, що Linq не працює ідеально з асинхронним кодом?
Ehsan Sajjad

2
@EhsanSajjad: Оскільки LINQ to Objects синхронно працює над об'єктами в пам'яті. Деякі обмежені речі працюють, наприклад Select. Але більшість ні, як Where.
Stephen Cleary

4
@EhsanSajjad: Якщо операція заснована на введенні / виведенні, то ви можете використовувати asyncдля зменшення потоків; якщо він пов'язаний з процесором і вже знаходиться у фоновому потоці, то asyncне забезпечить жодної вигоди.
Стівен Клірі

9

Щоб розширити відповідь Стівена, я створив такий метод розширення, щоб зберегти вільний стиль LINQ. Потім ви можете це зробити

await someTasks.WhenAll()

namespace System.Linq
{
    public static class IEnumerableExtensions
    {
        public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
        {
            return Task.WhenAll(source);
        }
    }
}

10
Особисто я б назвав ваш метод розширенняToArrayAsync
торвін

4

Одне питання з Task.WhenAll полягає в тому, що це створило б паралелізм. У більшості випадків це може бути навіть краще, але іноді ви хочете цього уникнути. Наприклад, читання пакетних даних із БД та надсилання даних до якоїсь віддаленої веб-служби. Ви не хочете завантажувати всі пакети в пам'ять, але натискаєте БД після обробки попередньої партії. Отже, вам доведеться порушити асинхронність. Ось приклад:

var events = Enumerable.Range(0, totalCount/ batchSize)
   .Select(x => x*batchSize)
   .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
   .SelectMany(x => x);
foreach (var carEvent in events)
{
}

Примітка .GetAwaiter (). GetResult () перетворюючи його на синхронозу. БД вдариться ліниво лише після обробки batchSize подій.



Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.