Berikut urutan cuplikan kode yang baru-baru ini saya gunakan untuk menggambarkan perbedaan dan berbagai masalah menggunakan pemecahan asinkron.
Misalkan Anda memiliki beberapa event handler di aplikasi berbasis GUI yang membutuhkan banyak waktu, sehingga Anda ingin membuatnya asynchronous. Inilah logika sinkron yang Anda mulai:
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItem mengembalikan sebuah Tugas, yang pada akhirnya akan menghasilkan beberapa hasil yang ingin Anda periksa. Jika hasil saat ini adalah yang Anda cari, perbarui nilai beberapa penghitung di UI, dan kembali dari metode. Jika tidak, Anda terus memproses lebih banyak item dari LoadNextItem.
Ide pertama untuk versi asynchronous: cukup gunakan lanjutan! Dan mari kita abaikan bagian perulangan untuk saat ini. Maksud saya, apa yang mungkin salah?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
Hebat, sekarang kami memiliki metode yang tidak memblokir! Itu malah crash. Setiap pembaruan pada kontrol UI harus terjadi pada UI thread, jadi Anda harus memperhitungkannya. Untungnya, ada opsi untuk menentukan bagaimana kelanjutan harus dijadwalkan, dan ada opsi default hanya untuk ini:
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
Hebat, sekarang kami memiliki metode yang tidak macet! Itu gagal secara diam-diam. Kelanjutan adalah tugas yang terpisah, dengan statusnya tidak terikat dengan tugas sebelumnya. Jadi, meskipun LoadNextItem gagal, pemanggil hanya akan melihat tugas yang telah berhasil diselesaikan. Oke, lalu teruskan pengecualiannya, jika ada:
return LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
throw t.Exception.InnerException;
}
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
Hebat, sekarang ini benar-benar berfungsi. Untuk satu item. Sekarang, bagaimana dengan perulangan itu. Ternyata, solusi yang setara dengan logika versi sinkron asli akan terlihat seperti ini:
Task AsyncLoop() {
return AsyncLoopTask().ContinueWith(t =>
Counter.Value = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
var tcs = new TaskCompletionSource<int>();
DoIteration(tcs);
return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
tcs.TrySetException(t.Exception.InnerException);
} else if (t.Result.Contains("target")) {
tcs.TrySetResult(t.Result.Length);
} else {
DoIteration(tcs);
}});
}
Atau, alih-alih semua hal di atas, Anda dapat menggunakan asinkron untuk melakukan hal yang sama:
async Task AsyncLoop() {
while (true) {
string result = await LoadNextItem();
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
}
Itu jauh lebih bagus sekarang, bukan?
Wait
panggilan pada contoh kedua maka kedua cuplikan tersebut akan (sebagian besar) setara.