Uruchamianie wielu zadań asynchronicznych i oczekiwanie na ich zakończenie


265

Muszę uruchomić wiele zadań asynchronicznych w aplikacji konsoli i poczekać na zakończenie wszystkich zadań przed dalszym przetwarzaniem.

Istnieje wiele artykułów, ale im bardziej czytam, tym bardziej się mylę. Przeczytałem i rozumiem podstawowe zasady biblioteki Zadań, ale najwyraźniej gdzieś brakuje linku.

Rozumiem, że możliwe jest łączenie zadań w taki sposób, aby zaczynały się po ukończeniu kolejnego (co jest w zasadzie scenariuszem dla wszystkich artykułów, które przeczytałem), ale chcę, aby wszystkie moje zadania działały jednocześnie i chcę to wiedzieć raz wszystkie są ukończone.

Jaka jest najprostsza implementacja dla takiego scenariusza?

Odpowiedzi:


440

Obie odpowiedzi nie wspomniały o oczekiwanym Task.WhenAll:

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

await Task.WhenAll(task1, task2);

Główna różnica między Task.WaitAlli Task.WhenAllpolega na tym, że pierwsze blokuje (podobnie jak Waitw przypadku pojedynczego zadania), podczas gdy drugie nie może i może być oczekiwane, dając kontrolę nad dzwoniącym, aż wszystkie zadania zakończą się.

Co więcej, obsługa wyjątków różni się:

Task.WaitAll:

Przynajmniej jedna z instancji Zadania została anulowana lub zgłoszony został wyjątek podczas wykonywania co najmniej jednej z instancji Zadania. Jeśli zadanie zostało anulowane, AggregateException zawiera OperationCanceledException w kolekcji InnerExceptions.

Task.WhenAll:

Jeśli którekolwiek z dostarczonych zadań zostaną zakończone w stanie błędu, zwrócone zadanie zostanie również ukończone w stanie Usterka, w którym jego wyjątki będą zawierać agregację zestawu nieopakowanych wyjątków z każdego z dostarczonych zadań.

Jeśli żadne z dostarczonych zadań nie spowodowało błędu, ale co najmniej jedno z nich zostało anulowane, zwrócone zadanie zakończy się w stanie Anulowane.

Jeśli żadne z zadań nie uległo awarii i żadne z zadań nie zostało anulowane, wynikowe zadanie zakończy się w stanie RanToCompletion. Jeśli podana tablica / wyliczenie nie zawiera żadnych zadań, zwrócone zadanie natychmiast przejdzie w stan RanToCompletion, zanim zostanie zwrócone do programu wywołującego.


4
Kiedy próbuję, moje zadania działają sekwencyjnie? Czy każde zadanie trzeba wcześniej rozpocząć indywidualnie await Task.WhenAll(task1, task2);?
Zapnologica

4
@Zapnologica Task.WhenAllnie uruchamia zadań dla Ciebie. Musisz podać je jako „gorące”, co oznacza, że ​​już się rozpoczęło.
Yuval Itzchakov

2
Dobrze. To ma sens. Więc co zrobi twój przykład? Ponieważ ich nie uruchomiłeś?
Zapnologica

2
@YuvalItzchakov bardzo dziękuję! To takie proste, ale dzisiaj bardzo mi pomogło! Jest wart co najmniej +1000 :)
Daniel Dušek

1
@Pierre Nie obserwuję. Co StartNewi spinning nowych zadań ma wspólnego z asynchronicznym czekaniem na nich wszystkich?
Yuval Itzchakov

106

Możesz utworzyć wiele zadań, takich jak:

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

Task.WaitAll(TaskList.ToArray());

48
Polecam WhenAll
Ravi

Czy można uruchomić wiele nowych wątków jednocześnie, używając słowa kluczowego Oczekiwanie zamiast .Start ()?
Matt W

1
@MattW Nie, gdy użyjesz funkcji czekaj, będzie czekać na jej zakończenie. W takim przypadku nie można utworzyć środowiska wielowątkowego. To jest powód, dla którego wszystkie zadania są czekane na końcu pętli.
Wirus

5
Głosuj za przyszłymi czytelnikami, ponieważ nie jest jasne, że jest to połączenie blokujące.
JRoughan

Zobacz zaakceptowaną odpowiedź z powodów, dla których nie możesz tego zrobić.
EL MOJO,

26

Najlepszą opcją, jaką widziałem, jest następująca metoda rozszerzenia:

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

Nazwij to tak:

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

Lub z asynchroniczną lambda:

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

26

Możesz użyć, WhenAllktóry zwróci oczekiwany Tasklub WaitAllktóry nie ma typu zwrotu i zablokuje dalsze wykonywanie kodu podobnie do Thread.Sleepmomentu, aż wszystkie zadania zostaną zakończone, anulowane lub zakłócone.

wprowadź opis zdjęcia tutaj

Przykład

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

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

Jeśli chcesz uruchomić zadania w praktycznej kolejności, możesz uzyskać inspirację z tego odpowiedzi .


przepraszam za spóźnienie na imprezę, ale dlaczego masz awaitza każdą operację i jednocześnie użyj WaitAlllub WhenAll. Czy zadania Task[]inicjujące nie powinny być bez await?
dee zg

@dee zg Masz rację. Oczekiwanie powyżej pokonuje cel. Zmienię swoją odpowiedź i usunę je.
NtFreX

tak, to jest to. dzięki za wyjaśnienie! (głosowanie za miłą odpowiedzią)
dee zg

8

Czy chcesz Taskpołączyć łańcuchy , czy można je wywoływać równolegle?

Do łączenia
Po prostu zrób coś takiego

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

i nie zapomnij sprawdzić poprzedniej Taskinstancji w każdej z nich, ContinueWithponieważ może to być wina.

Dla równoległego sposobu
Najprostsza metoda, na którą natknąłem się: w Parallel.Invoke przeciwnym razie Task.WaitAllmożesz użyć WaitHandles do odliczania do zera akcji (poczekaj, jest nowa klasa:) CountdownEventlub ...


3
Doceń odpowiedź, ale twoje sugestie można by wyjaśnić nieco więcej.
Daniel Minnaar

@drminnaar, jakie inne wyjaśnienie obok linków do msdn z przykładami potrzebujesz? nawet nie kliknąłeś linków, prawda?
Andreas Niedermair

4
Kliknąłem linki i przeczytałem treść. Miałem ochotę na Invoke, ale było wiele If i But chodziło o to, czy działa asynchronicznie, czy nie. Ciągle edytujesz swoją odpowiedź. Link WaitAll, który opublikowałeś był doskonały, ale wybrałem odpowiedź, która wykazała tę samą funkcjonalność w szybszy i łatwiejszy do odczytania sposób. Nie obrażaj się, twoja odpowiedź wciąż stanowi dobrą alternatywę dla innych podejść.
Daniel Minnaar

@drminnaar bez obrazy wzięty tutaj, jestem po prostu ciekawy :)
Andreas Niedermair

5

Tak to robię za pomocą tablicy 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
Dlaczego nie zachowasz go jako Tablicy zadań?
Talha Talip Açıkgöz

1
Jeśli nie jesteś ostrożny @ talha-talip-açıkgöz, wykonaj zadania, których się nie spodziewałeś. Robiąc to jako delegat Func, jasno określa twoje zamiary.
DalSoft,

5

Jeszcze jedna odpowiedź ... ale zwykle znajduję się w przypadku, gdy muszę jednocześnie ładować dane i umieszczać je w zmiennych, takich jak:

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
Jeśli LoadCatsAsync()i LoadDogAsync()to tylko połączenia z bazą danych, są one powiązane z operacjami we / wy. Task.Run()jest przeznaczony do pracy z procesorem; dodaje dodatkowy niepotrzebny narzut, jeśli wszystko, co robisz, to czeka na odpowiedź z serwera bazy danych. Akceptowana odpowiedź Yuvala to właściwy sposób na pracę związaną z IO.
Stephen Kennedy,

@StephenKennedy, czy możesz wyjaśnić, jaki rodzaj kosztów ogólnych i jak bardzo może to wpłynąć na wydajność? Dzięki!
Yehor Hromadskyi,

Trudno byłoby to podsumować w polu komentarzy :) Zamiast tego polecam przeczytanie artykułów Stephena Cleary'ego - on jest ekspertem w tej dziedzinie. Zacznij tutaj: blog.stephencleary.com/2013/10/…
Stephen Kennedy

-1

Przygotowałem kawałek kodu, aby pokazać, jak korzystać z zadania w niektórych z tych scenariuszy.

    // 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
jak uzyskać wyniki zadań? Na przykład, aby scalić „wiersze” (z N zadań równolegle) w danych i powiązać je z gridview asp.net?
PreguntonCojoneroCabrón
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.