Menjalankan beberapa tugas async dan menunggu semuanya selesai

265

Saya perlu menjalankan beberapa tugas async di aplikasi konsol, dan menunggu semuanya selesai sebelum diproses lebih lanjut.

Ada banyak artikel di luar sana, tetapi saya tampaknya semakin bingung semakin saya membaca. Saya telah membaca dan memahami prinsip-prinsip dasar pustaka Tugas, tapi saya jelas kehilangan tautan di suatu tempat.

Saya mengerti bahwa mungkin untuk melakukan tugas-tugas rantai sehingga mereka mulai setelah selesai lainnya (yang merupakan skenario untuk semua artikel yang saya baca), tapi saya ingin semua Tugas saya berjalan pada waktu yang sama, dan saya ingin tahu sekali mereka semua selesai.

Apa implementasi paling sederhana untuk skenario seperti ini?

Daniel Minnaar
sumber

Jawaban:

440

Kedua jawaban tidak menyebutkan yang ditunggu-tunggu Task.WhenAll:

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

await Task.WhenAll(task1, task2);

Perbedaan utama antara Task.WaitAlldan Task.WhenAlladalah bahwa yang pertama akan memblokir (mirip dengan menggunakan Waitpada satu tugas) sedangkan yang terakhir tidak akan dan dapat ditunggu, menghasilkan kontrol kembali ke pemanggil sampai semua tugas selesai.

Lebih dari itu, penanganan pengecualian berbeda:

Task.WaitAll:

Setidaknya satu instance Task dibatalkan - atau pengecualian dilemparkan selama eksekusi setidaknya satu instance Task. Jika tugas dibatalkan, AggregateException berisi OperationCanceledException dalam koleksi InnerExceptions-nya.

Task.WhenAll:

Jika salah satu dari tugas yang disediakan menyelesaikan dalam kondisi yang salah, tugas yang dikembalikan juga akan selesai dalam keadaan yang salah, di mana pengecualiannya akan berisi agregasi dari set pengecualian yang terbuka dari masing-masing tugas yang disediakan.

Jika tidak ada tugas yang diberikan salah tetapi setidaknya salah satu dari mereka dibatalkan, tugas yang dikembalikan akan berakhir pada status Dibatalkan.

Jika tidak ada tugas yang salah dan tidak ada tugas yang dibatalkan, tugas yang dihasilkan akan berakhir pada status RanToCompletion. Jika array / enumerable yang disediakan tidak berisi tugas, tugas yang dikembalikan akan segera beralih ke status RanToCompletion sebelum dikembalikan ke pemanggil.

Yuval Itzchakov
sumber
4
Ketika saya mencoba ini, tugas saya berjalan berurutan? Apakah seseorang harus memulai setiap tugas secara individual sebelumnya await Task.WhenAll(task1, task2);?
Zapnologica
4
@Zapnologica Task.WhenAlltidak memulai tugas untuk Anda. Anda harus memberi mereka "panas", artinya sudah dimulai.
Yuval Itzchakov
2
Baik. Itu masuk akal. Jadi apa yang akan dilakukan contoh Anda? Karena Anda belum memulainya?
Zapnologica
2
@YuvalItzchakov terima kasih banyak! Ini sangat sederhana tetapi sangat membantu saya hari ini!
Bernilai
1
@Pierre saya tidak mengikuti. Apa yang harus dilakukan StartNewdan memutar tugas baru dengan menunggu secara tidak sinkron pada semuanya?
Yuval Itzchakov
106

Anda dapat membuat banyak tugas seperti:

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

Task.WaitAll(TaskList.ToArray());
Virus
sumber
48
Saya akan merekomendasikan WhenAll
Ravi
Apakah mungkin untuk memulai beberapa utas baru, pada saat yang sama, menggunakan kata kunci yang menunggu daripada. Mulai ()?
Matt W
1
@ MatW Tidak, saat Anda menggunakan menunggu, itu akan menunggu sampai selesai. Dalam hal ini Anda tidak akan dapat membuat lingkungan multi-utas. Ini adalah alasan bahwa semua tugas menunggu di akhir perulangan.
Virus
5
Turunkan unduhan untuk pembaca di masa mendatang karena tidak dijelaskan bahwa ini adalah panggilan yang memblokir.
JRoughan
Lihat jawaban yang diterima untuk alasan mengapa tidak melakukan ini.
EL MOJO
26

Pilihan terbaik yang pernah saya lihat adalah metode ekstensi berikut:

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

Sebut saja seperti ini:

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

Atau dengan lambda async:

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

Anda dapat menggunakan WhenAllyang akan mengembalikan yang ditunggu-tunggu Taskatau WaitAllyang tidak memiliki tipe pengembalian dan akan memblokir eksekusi kode lebih lanjut secara simultan Thread.Sleepsampai semua tugas selesai, dibatalkan atau salah.

masukkan deskripsi gambar di sini

Contoh

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

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

Jika Anda ingin menjalankan tugas dalam urutan praticular, Anda bisa mendapatkan inspirasi dari server ini .

NtFreX
sumber
maaf karena datang ke pesta terlambat tetapi, mengapa Anda memiliki awaituntuk setiap operasi dan pada saat yang sama menggunakan WaitAllatau WhenAll. Bukankah seharusnya tugas Task[]inisialisasi tanpa await?
dee zg
@ ya zg Anda benar. Menunggu di atas mengalahkan tujuan. Saya akan mengubah jawaban saya dan menghapusnya.
NtFreX
ya itu saja. terima kasih atas klarifikasi! (upvote untuk jawaban yang bagus)
dee zg
8

Apakah Anda ingin rantai Tasks, atau mereka dapat dipanggil secara paralel?

Untuk merantai
Lakukan saja sesuatu seperti

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

dan jangan lupa untuk memeriksa Taskcontoh sebelumnya di masing ContinueWith- masing karena mungkin salah.

Untuk cara paralel
Metode paling sederhana yang saya temui: Parallel.Invoke Kalau tidak ada Task.WaitAllatau Anda bahkan dapat menggunakan WaitHandles untuk melakukan hitung mundur ke nol tindakan yang tersisa (tunggu, ada kelas baru:) CountdownEvent, atau ...

Andreas Niedermair
sumber
3
Hargai jawabannya, tetapi saran Anda bisa dijelaskan sedikit lagi.
Daniel Minnaar
@drminnaar mana penjelasan lain selain tautan ke msdn dengan contoh yang Anda butuhkan? Anda bahkan tidak mengklik tautannya, bukan?
Andreas Niedermair
4
Saya mengklik tautan, dan saya membaca kontennya. Saya akan mengikuti Invoke, tetapi ada banyak If dan But tentang apakah ini berjalan secara tidak sinkron atau tidak. Anda mengedit jawaban Anda terus menerus. Tautan WaitAll yang Anda poskan sempurna, tetapi saya memilih jawaban yang menunjukkan fungsi yang sama dengan cara yang lebih cepat dan mudah dibaca. Jangan tersinggung, jawaban Anda masih memberikan alternatif yang baik untuk pendekatan lain.
Daniel Minnaar
@drminnaar jangan tersinggung di sini, saya hanya ingin tahu :)
Andreas Niedermair
5

Ini adalah bagaimana saya melakukannya dengan Fungsi array <> :

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
DalSoft
sumber
1
Mengapa Anda tidak menyimpannya sebagai Task Array?
Talha Talip Açıkgöz
1
Jika @ talha-talip-açıkgöz Anda tidak berhati-hati, Anda mengeksekusi Tugas ketika Anda tidak mengharapkannya dieksekusi. Melakukannya sebagai delegasi Fungsi membuat maksud Anda jelas.
DalSoft
5

Namun jawaban lain ... tapi saya biasanya menemukan diri saya dalam sebuah kasus, ketika saya perlu memuat data secara bersamaan dan memasukkannya ke dalam variabel, seperti:

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
}
Yehor Hromadskyi
sumber
1
Jika LoadCatsAsync()dan LoadDogAsync()hanya panggilan basis data, mereka terikat IO. Task.Run()adalah untuk pekerjaan yang terikat CPU; itu menambah biaya tambahan yang tidak perlu jika semua yang Anda lakukan menunggu jawaban dari server database. Jawaban yang diterima Yuval adalah cara yang tepat untuk pekerjaan yang terikat IO.
Stephen Kennedy
@StephenKennedy dapatkah Anda mengklarifikasi jenis overhead dan seberapa besar dampaknya terhadap kinerja? Terima kasih!
Yehor Hromadskyi
Itu akan sangat sulit untuk diringkas dalam kotak komentar :) Sebaliknya saya sarankan membaca artikel-artikel Stephen Cleary - dia ahli dalam hal ini. Mulai di sini: blog.stephencleary.com/2013/10/...
Stephen Kennedy
-1

Saya menyiapkan sepotong kode untuk menunjukkan kepada Anda bagaimana menggunakan tugas untuk beberapa skenario ini.

    // 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);
    }
sayah imad
sumber
1
bagaimana mendapatkan hasil dari Tugas? Misalnya, untuk menggabungkan "baris" (dari N tugas secara paralel) dalam data dan mengikatnya ke gridview asp.net?
PreguntonCojoneroCabrón