Запуск декількох завдань асинхронізації та очікування їх завершення


265

Мені потрібно запустити кілька завдань на асинхронізацію в консольному додатку та дочекатися їх завершення перед подальшою обробкою.

Статей там багато, але я, мабуть, більше плутаю, чим більше читаю. Я прочитав і зрозумів основні принципи бібліотеки завдань, але явно десь пропускаю посилання.

Я розумію, що можна ланцюжок завдань таким чином, щоб вони починалися після того, як закінчується інше (що в значній мірі є сценарієм для всіх прочитаних статей), але я хочу, щоб усі мої завдання виконувалися одночасно, і я хочу знати це один раз вони всі завершені.

Яка найпростіша реалізація для такого сценарію?

Відповіді:


440

Обидві відповіді не згадували очікуваного Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

Основна відмінність між Task.WaitAllі Task.WhenAllполягає в тому, що перший заблокує (подібно до використання Waitв одному завданні), тоді як останнього не буде і на нього можна очікувати, повертаючи управління абоненту, поки всі завдання не закінчаться.

Тим більше, що обробка виключень відрізняється:

Task.WaitAll:

Принаймні один із екземплярів Завдання було скасовано - або виняток було викинуто під час виконання принаймні одного з екземплярів Завдання. Якщо завдання було скасовано, AggregateException містить в своїй колекції InnerExceptions OperationCanceledException.

Task.WhenAll:

Якщо будь-яке із заданих завдань завершиться у несправному стані, повернене завдання також буде виконане у стані Faulted, де його винятки містять агрегацію набору нерозгорнутих винятків з кожної із заданих задач.

Якщо жодне із поданих завдань не вийшло, але принаймні одне з них було скасовано, повернене завдання закінчиться у відміненому стані.

Якщо жодне із завдань не вийшло з ладу і жодне із завдань не було скасовано, отримане завдання закінчиться у стані RanToCompletion. Якщо наданий масив / перечислювальний ряд не містить завдань, повернене завдання негайно перейде до стану RanToCompletion, перш ніж воно повернеться до абонента.


4
Коли я спробую це, мої завдання виконуються послідовно? Чи потрібно починати кожне завдання окремо раніше await Task.WhenAll(task1, task2);?
Zapnologica

4
@Zapnologica Task.WhenAllне починає завдання для вас. Ви повинні надати їм "гарячі", тобто вже розпочаті.
Юваль Ітчаков

2
Гаразд. Що має сенс. То що робитиме ваш приклад? Тому що ви їх не починали?
Zapnologica

2
@YuvalItzchakov дуже дякую! Це так просто, але сьогодні мені це дуже допомогло! Варто як мінімум +1000 :)
Даніель Душек

1
@Pierre Я не слідкую. Що стосується StartNewі спінінг нових завдань, пов'язаних з асинхронним очікуванням на них усіх?
Ювал Ітчаков

106

Ви можете створити багато завдань, таких як:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
Я б рекомендував WhenAll
Ravi

Чи можливо запустити кілька нових потоків одночасно, використовуючи ключове слово очікування, а не .Start ()?
Метт W

1
@MattW Ні, коли ви використовуєте функцію очікування, вона чекає її завершення. У цьому випадку ви б не змогли створити багатопотокове середовище. Це причина, що всі завдання чекають в кінці циклу.
Вірус

5
Оцінка для майбутніх читачів, оскільки не стало зрозуміло, що це блокування дзвінка.
JRoughan

Дивіться прийняту відповідь з причин, чому цього не зробити.
EL MOJO

26

Найкращий варіант, який я бачив, - це наступний метод розширення:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

Назвіть це так:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

Або з лямбда-асинхрією:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

Ви можете скористатися WhenAllфункцією, яка поверне очікуваний Taskабо WaitAllякий не має типу повернення, і заблокує подальше виконання коду синхронізовано до Thread.Sleepтих пір, поки всі завдання не будуть виконані, скасовані або не виконані помилки.

введіть тут опис зображення

Приклад

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

Якщо ви хочете виконати завдання в прямокутному порядку, ви можете отримати натхнення з цього прихильника.


вибачте за те, що пізно приїхали на вечірку, але чому ви маєте awaitдля кожної операції і одночасно користуєтесь WaitAllабо WhenAll. Чи не повинні бути завдання Task[]без ініціалізації без await?
dee zg

@dee zg Ви праві. Очікування вище перемагає мету. Я зміню свою відповідь і видалю їх.
NtFreX

так, це все. дякую за роз’яснення! (нагорода за гарну відповідь)
dee zg

8

Ви хочете зв'язати ланцюги Task, або їх можна викликати паралельно?

Для прикування
просто зробіть щось на кшталт

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

і не забудьте перевірити попередній Taskекземпляр у кожному, ContinueWithоскільки це може бути несправним.

Для паралельного способу
Найпростіший метод, який я натрапив: Parallel.Invoke інакше є Task.WaitAllабо ви навіть можете використовувати WaitHandles для виконання відліку до нульових дій (зачекайте, є новий клас CountdownEvent:), або ...


3
Вдячні за відповідь, але ваші пропозиції можна було б пояснити трохи більше.
Даніель Міннаар

@drminnaar, яке інше пояснення поруч із посиланнями на msdn із прикладами вам потрібно? ви навіть не натискали на посилання, чи не так?
Andreas Niedermair

4
Я натискав на посилання, і читав вміст. Я збирався на Invoke, але було багато If і and But про те, чи працює він асинхронно чи ні. Ви постійно редагували свою відповідь. Посилання WaitAll, яке ви опублікували, було ідеальним, але я попросив відповісти, що продемонстрував ту саму функціональність більш швидким та легким для читання способом. Не ображайтесь, Ваша відповідь все ще пропонує хороші альтернативи іншим підходам.
Даніель Міннаар

@drminnaar тут не вчинили жодних злочинів, мені просто цікаво :)
Andreas Niedermair

5

Ось так я це роблю з масивом Func <> :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
Чому ви просто не збережете це як масив завдань?
Talha Talip Açıkgöz

1
Якщо ваш необережний @ talha-talip-açıkgöz виконує завдання, коли ви не очікували їх виконання. Виконання цього в якості делегата Func робить ваш намір зрозумілим.
DalSoft

5

Ще одна відповідь ... але я зазвичай опиняюсь у випадку, коли мені потрібно одночасно завантажувати дані і вводити їх у змінні, наприклад:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
Якщо LoadCatsAsync()і LoadDogAsync()є лише дзвінками до бази даних, вони пов'язані IO. Task.Run()призначений для роботи з процесором; це додає додаткові непотрібні накладні витрати, якщо все, що ви робите, чекає відповіді від сервера баз даних. Прийнята відповідь Юваля - це правильний шлях для роботи, пов'язаної з ІО.
Стівен Кеннеді

@StephenKennedy Ви можете, будь ласка, уточнити, що таке накладні витрати та на скільки це може вплинути на продуктивність? Дякую!
Єгор Громадський

Це було б досить важко підсумувати у вікні коментарів :) Натомість я рекомендую прочитати статті Стівена Клірі - він експерт у цій справі. Почніть тут: blog.stephencleary.com/2013/10/…
Стівен Кеннеді

-1

Я підготував фрагмент коду, щоб показати вам, як використовувати завдання для деяких із цих сценаріїв.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
як отримати результати Завдань? Наприклад, об'єднати "рядки" (з N завдань паралельно) у таблицю даних і прив'язати її до gridview asp.net?
PreguntonCojoneroCabrón
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.