Mengelola tugas asinkron dan paralel
Untuk pengembang C#, Pustaka Paralel Tugas (TPL) menyediakan cara yang lebih mudah untuk menulis kode paralel. Namun, tidak semua kode cocok untuk paralelisasi. Misalnya, jika perulangan hanya melakukan sejumlah kecil pekerjaan pada setiap iterasi, atau tidak dijalankan untuk banyak iterasi, maka overhead paralelisasi dapat menyebabkan kode berjalan lebih lambat. Selain itu, paralelisasi, seperti kode multithreaded apa pun, menambahkan kompleksitas ke eksekusi program Anda.
Jepitan umum dalam paralelisme data dan tugas
Dalam banyak kasus, Parallel.For dan Parallel.ForEach dapat memberikan peningkatan performa yang signifikan atas perulangan berurutan biasa. Namun, pekerjaan memparalelkan perulangan memperkenalkan kompleksitas yang dapat menyebabkan masalah yang tidak umum dalam kode berurutan.
Jangan berasumsi bahwa paralel selalu lebih cepat
Dalam kasus tertentu, perulangan paralel mungkin berjalan lebih lambat daripada versi sekuensialnya. Aturan praktis dasar adalah bahwa perulangan paralel yang memiliki beberapa iterasi dan delegasi pengguna yang cepat tidak mungkin meningkatkan kinerja secara signifikan. Namun, karena banyak faktor yang terlibat dalam performa, kami sarankan Anda selalu mengukur hasil aktual.
Hindari menulis ke lokasi memori bersama
Dalam kode berurutan, tidak jarang membaca dari atau menulis ke variabel statis atau bidang kelas. Namun, setiap kali beberapa utas mengakses variabel tersebut secara bersamaan, ada potensi signifikan untuk kondisi balapan. Meskipun Anda dapat menggunakan kunci untuk menyinkronkan akses ke variabel, biaya sinkronisasi dapat merusak performa. Oleh karena itu, kami sarankan Anda menghindari, atau setidaknya membatasi, akses ke status berbagi sebanyak mungkin dalam perulangan paralel. Cara terbaik untuk melakukan ini adalah dengan menggunakan kelebihan beban Parallel.For dan Parallel.ForEach yang menggunakan System.Threading.ThreadLocal<T> variabel untuk menyimpan status thread-local selama eksekusi perulangan.
Hindari paralelisasi berlebihan
Dengan menggunakan perulangan paralel, Anda memikul biaya overhead untuk mempartisi kumpulan data sumber dan menyinkronkan utas pekerja. Manfaat paralelisasi dibatasi lebih lanjut oleh jumlah prosesor pada komputer. Tidak ada peningkatan kecepatan yang akan diperoleh dengan menjalankan beberapa utas yang memerlukan komputasi hanya pada satu prosesor. Oleh karena itu, Anda harus berhati-hati agar tidak terlalu banyak melakukan paralelisasi pada sebuah perulangan.
Skenario paling umum di mana paralelisasi berlebihan dapat terjadi adalah dalam perulangan berlapis. Dalam kebanyakan kasus, yang terbaik adalah hanya memparalelisasi perulangan luar kecuali satu atau beberapa kondisi berikut berlaku:
- Perulangan bagian dalam diketahui panjang.
- Anda melakukan komputasi yang mahal pada setiap pesanan.
- Sistem target diketahui memiliki prosesor yang cukup untuk menangani jumlah utas yang diproduksi dengan paralelisasi pemrosesan.
Dalam semua kasus, cara terbaik untuk menentukan bentuk kueri optimal adalah dengan menguji dan mengukur.
Penanganan pengecualian dalam tugas asinkron dan paralel
Saat Anda menggunakan Pustaka Paralel Tugas (TPL) untuk menjalankan tugas, pengecualian dapat terjadi dengan beberapa cara berbeda. Yang paling umum adalah ketika pekerjaan menghasilkan pengecualian. Melemparkan pengecualian dapat terjadi ketika tugas berjalan pada utas kumpulan utas atau saat berjalan di utas utama. Dalam kedua kasus, pengecualian disebarkan kembali ke utas panggilan.
Ketika Anda menggunakan metode Task.Wait untuk menunggu tugas selesai, pengecualian apa pun yang dilemparkan oleh tugas tersebut disebarkan kembali ke utas panggilan. Anda dapat menangani pengecualian ini menggunakan blok coba/tangkap. Jika tugas adalah induk dari tugas anak yang terlampir, atau jika Anda menunggu beberapa tugas, dapat terjadi beberapa pengecualian. Jika satu atau lebih pengecualian dilemparkan, pengecualian tersebut dibungkus dalam sebuah instance AggregateException.
Pengecualian AggregateException memiliki properti InnerExceptions yang dapat dienumerasi untuk memeriksa semua pengecualian asli yang dilemparkan, dan menangani (atau tidak menangani) masing-masing satu per satu.
Contoh berikut menunjukkan cara menangani pengecualian yang dilemparkan oleh tugas.
public static partial class Program
{
public static void Main()
{
HandleThree();
}
public static void HandleThree()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
// Rethrow any other exception.
else
{
throw ex;
}
}
}
}
}
// Define the CustomException class
public class CustomException : Exception
{
public CustomException(string message) : base(message) { }
}
// The example displays the following output:
// This exception is expected!
Dalam contoh ini, metode HandleThree membuat tugas yang melemparkan sebuah CustomException. Blok try/catch menangkap AggregateException dan mengiterasi melalui koleksi InnerExceptions. Jika pengecualian berjenis CustomException, itu mencetak pesan ke konsol. Jika ada jenis pengecualian lain, itu melemparkannya kembali.
Anda juga dapat menangani pengecualian orisinal dengan menggunakan metode AggregateException.Handle. Metode ini menerima delegasi yang dipanggil untuk setiap pengecualian dalam himpunan InnerExceptions. Jika delegat mengembalikan true, pengecualian dianggap telah ditangani dan dihapus dari kumpulan. Jika mengembalikan false, pengecualian akan diteruskan kembali.
Contoh berikut menunjukkan cara menggunakan Handle metode untuk menangani pengecualian yang dilemparkan oleh tugas.
public static partial class Program
{
public static void HandleFour()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
try
{
task.Wait();
}
catch (AggregateException ae)
{
ae.Handle(ex =>
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
return true;
}
// Rethrow any other exception.
return false;
});
}
}
}
Dalam contoh ini, metode HandleFour membuat tugas yang melemparkan sebuah CustomException. Blok try/catch menangkap AggregateException dan memanggil metode Handle. Delegasi memeriksa apakah pengecualian berjenis CustomException. Jika pengecualian berjenis CustomException, delegasi akan mencetak pesan ke konsol dan mengembalikan true. Respons true menunjukkan bahwa pengecualian telah ditangani. Jika pengecualian adalah jenis pengecualian lain, delegasi mengembalikan false, menyebabkan pengecualian dilemparkan kembali.
Ringkasan
Unit ini menjelaskan situasi ketika kode tidak cocok untuk paralelisasi dan membahas perangkap umum dalam paralelisme data dan tugas. Misalnya, dengan menganggap bahwa paralel selalu lebih cepat, penulisan ke lokasi memori bersama, dan pemanfaatan paralel yang berlebihan. Konten ini juga menjelaskan cara menangani pengecualian dalam tugas asinkron dan paralel, termasuk cara menggunakan Task.Wait metode dan AggregateException.Handle metode .
Poin-poin penting
- Tidak semua kode cocok untuk paralelisasi. Menguji dan mengukur performa sangat penting sebelum paralelisasi kode.
- Jebakan umum dalam paralelisme data dan tugas termasuk dengan asumsi paralel selalu lebih cepat, menulis ke lokasi memori bersama, dan paralelisasi berlebihan.
- Pengecualian dalam tugas asinkron dan paralel dapat ditangani menggunakan
Task.Waitmetode danAggregateException.Handlemetode . - Pengecualian
AggregateExceptionmemilikiInnerExceptionsproperti yang dapat dijumlahkan untuk memeriksa semua pengecualian asli yang dilemparkan.