Kapan menggunakan Task.WaitAll vs. Task.WhenAll di .NET

TPL (Task Parallel Library) adalah salah satu fitur baru yang paling menarik yang ditambahkan dalam versi terbaru kerangka .NET. Metode Task.WaitAll dan Task.WhenAll adalah dua metode penting dan sering digunakan di TPL.

The Task.WaitAll memblokir utas saat ini hingga semua tugas lain telah menyelesaikan eksekusi. Metode Task.WhenAll digunakan untuk membuat tugas yang akan selesai jika dan hanya jika semua tugas lainnya telah selesai.

Jadi, jika Anda menggunakan Task.WhenAll Anda akan mendapatkan objek tugas yang belum selesai. Namun, itu tidak akan memblokir tetapi akan memungkinkan program untuk dijalankan. Sebaliknya, panggilan metode Task.WaitAll sebenarnya memblokir dan menunggu semua tugas lainnya selesai.

Pada dasarnya, Task.WhenAll akan memberi Anda tugas yang belum selesai, tetapi Anda dapat menggunakan ContinueWith segera setelah tugas yang ditentukan telah menyelesaikan pelaksanaannya. Perhatikan bahwa baik Task.WhenAll maupun Task.WaitAll tidak akan benar-benar menjalankan tugas; yaitu, tidak ada tugas yang dimulai dengan metode ini. Berikut adalah cara ContinueWith digunakan dengan Task.WhenAll: 

Task.WhenAll (taskList) .ContinueWith (t => {

  // tulis kode Anda di sini

});

Seperti yang dinyatakan dalam dokumentasi Microsoft, Task.WhenAll "membuat tugas yang akan selesai ketika semua objek Tugas dalam koleksi yang tidak terhitung telah selesai".

Task.WhenAll vs. Task.WaitAll

Izinkan saya menjelaskan perbedaan antara kedua metode ini dengan contoh sederhana. Misalkan Anda memiliki tugas yang melakukan beberapa aktivitas dengan UI thread - katakanlah, beberapa animasi perlu ditampilkan di antarmuka pengguna. Sekarang, jika Anda menggunakan Task.WaitAll, antarmuka pengguna akan diblokir dan tidak akan diperbarui sampai semua tugas terkait selesai dan blok tersebut dilepaskan. Namun, jika Anda menggunakan Task.WhenAll dalam aplikasi yang sama, UI thread tidak akan diblokir dan akan diperbarui seperti biasa.

Jadi, metode mana yang harus Anda gunakan kapan? Nah, Anda bisa menggunakan WaitAll saat maksudnya memblokir secara sinkron untuk mendapatkan hasil. Tetapi ketika Anda ingin memanfaatkan asynchrony, Anda ingin menggunakan varian WhenAll. Anda dapat menunggu Task.WhenAll tanpa harus memblokir utas saat ini. Oleh karena itu, Anda mungkin ingin menggunakan await dengan Task.WhenAll di dalam metode async.

Saat Task.WaitAll memblokir utas saat ini hingga semua tugas yang tertunda selesai, Task.WhenAll mengembalikan objek tugas. Task.WaitAll menampilkan AggregateException saat satu atau beberapa tugas menampilkan pengecualian. Ketika satu atau beberapa tugas menampilkan pengecualian dan Anda menunggu metode Task.WhenAll, metode ini akan membuka bungkusan AggregateException dan hanya mengembalikan yang pertama.

Hindari menggunakan Task.Run dalam loop

Anda dapat menggunakan tugas jika Anda ingin menjalankan aktivitas bersamaan. Jika Anda membutuhkan tingkat paralelisme yang tinggi, tugas bukanlah pilihan yang baik. Itu selalu disarankan untuk menghindari penggunaan thread pool thread di ASP.Net. Oleh karena itu, Anda harus menahan diri untuk tidak menggunakan Task.Run atau Task.factory.StartNew di ASP.Net.

Task.Run harus selalu digunakan untuk kode terikat CPU. The Task.Run bukanlah pilihan yang baik dalam aplikasi ASP.Net, atau, aplikasi yang memanfaatkan runtime ASP.Net karena hanya memindahkan pekerjaan ke thread ThreadPool. Jika Anda menggunakan ASP.Net Web API, permintaan sudah menggunakan thread ThreadPool. Oleh karena itu, jika Anda menggunakan Task.Run di aplikasi ASP.Net Web API, Anda hanya membatasi skalabilitas dengan memindahkan pekerjaan ke thread pekerja lain tanpa alasan apa pun.

Perhatikan bahwa ada kerugian dalam menggunakan Task. Jalankan dalam satu lingkaran. Jika Anda menggunakan metode Task.Run di dalam loop, beberapa tugas akan dibuat - satu untuk setiap unit kerja atau iterasi. Namun, jika Anda menggunakan Parallel.ForEach sebagai pengganti menggunakan Task.Run di dalam loop, Partisi akan dibuat untuk menghindari pembuatan lebih banyak tugas untuk melakukan aktivitas daripada yang dibutuhkan. Ini dapat meningkatkan kinerja secara signifikan karena Anda dapat menghindari terlalu banyak sakelar konteks dan masih memanfaatkan banyak inti di sistem Anda.

Perlu dicatat bahwa Parallel.ForEach menggunakan Partitioner secara internal untuk mendistribusikan koleksi ke item pekerjaan. Kebetulan, distribusi ini tidak terjadi untuk setiap tugas dalam daftar item, melainkan terjadi sebagai batch. Ini menurunkan biaya overhead yang terlibat dan karenanya meningkatkan kinerja. Dengan kata lain, jika Anda menggunakan Task.Run atau Task.Factory.StartNew di dalam loop, mereka akan membuat tugas baru secara eksplisit untuk setiap iterasi dalam loop. Parallel.ForEach jauh lebih efisien karena akan mengoptimalkan eksekusi dengan mendistribusikan beban kerja ke beberapa inti dalam sistem Anda.