Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Model Task asynchronous programming (TAP) menyediakan lapisan abstraksi atas pengkodan asinkron yang khas. Dalam model ini, Anda menulis kode sebagai urutan pernyataan, sama seperti biasa. Perbedaannya adalah Anda dapat membaca kode berbasis tugas saat pengkompilasi memproses setiap pernyataan dan sebelum mulai memproses pernyataan berikutnya. Untuk mencapai model ini, pengkompilasi melakukan banyak transformasi untuk menyelesaikan setiap tugas. Beberapa pernyataan dapat memulai pekerjaan dan mengembalikan objek Task yang mewakili pekerjaan yang sedang berlangsung dan pengkompilasi harus menyelesaikan transformasi ini. Tujuan dari pemrograman tugas asinkron adalah untuk memungkinkan kode yang dapat dibaca seperti urutan pernyataan, tetapi dijalankan dalam urutan yang lebih rumit. Eksekusi didasarkan pada alokasi sumber daya eksternal dan ketika tugas selesai.
Model pemrograman asinkron tugas dianalogikan dengan bagaimana orang memberikan instruksi untuk proses yang menyertakan tugas asinkron. Artikel ini menggunakan contoh dengan instruksi untuk membuat sarapan untuk menunjukkan bagaimana kata kunci Async dan Await mempermudah alasan tentang kode yang mencakup serangkaian instruksi asinkron. Instruksi untuk membuat sarapan mungkin disediakan sebagai daftar:
- Tuangkan secangkir kopi.
- Panaskan wajan, lalu goreng dua butir telur.
- Masak tiga potongan hash brown.
- Panggang dua potong roti.
- Sebarkan mentega dan selai di roti panggang.
- Tuangkan segelas jus jeruk.
Jika Anda memiliki pengalaman dengan memasak, Anda mungkin menyelesaikan instruksi ini secara asinkron. Anda mulai menghangatkan panci untuk telur, lalu mulai memasak hash brown. Anda menaruh roti di pemanggang roti, lalu mulai memasak telur. Pada setiap langkah proses, Anda memulai tugas, lalu beralih ke tugas lain yang siap untuk anda perhatikan.
Memasak sarapan adalah contoh yang baik dari pekerjaan asinkron yang tidak paralel. Satu orang (atau utas) dapat menangani semua tugas. Satu orang dapat membuat sarapan secara asinkron dengan memulai tugas berikutnya sebelum tugas sebelumnya selesai. Setiap tugas memasak berlangsung terlepas dari apakah seseorang secara aktif menonton prosesnya. Segera setelah Anda mulai menghangatkan wajan untuk telur, Anda dapat mulai memasak rostis. Setelah hash brown mulai dimasak, Anda dapat memasukkan roti ke dalam pemanggang roti.
Untuk algoritma paralel, Anda memerlukan beberapa orang yang memasak (atau beberapa thread). Satu orang memasak telur, yang lain memasak hash browns, dan sebagainya. Setiap orang berfokus pada satu tugas tertentu. Setiap orang yang memasak (atau setiap utas) diblokir menunggu secara sinkron hingga tugas saat ini selesai: Hash browns siap dibalik, roti siap muncul dari pemanggang roti, dan sebagainya.
Pertimbangkan daftar instruksi sinkron yang sama yang ditulis sebagai pernyataan kode Visual Basic:
Sub Main()
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggs As Egg = FryEggs(2)
Console.WriteLine("eggs are ready")
Dim hashBrown As HashBrown = FryHashBrowns(3)
Console.WriteLine("hash browns are ready")
Dim toast As Toast = ToastBread(2)
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Sub
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
Private Function ToastBread(slices As Integer) As Toast
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Task.Delay(3000).Wait()
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Function FryHashBrowns(patties As Integer) As HashBrown
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Task.Delay(3000).Wait()
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Task.Delay(3000).Wait()
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Function FryEggs(howMany As Integer) As Egg
Console.WriteLine("Warming the egg pan...")
Task.Delay(3000).Wait()
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Task.Delay(3000).Wait()
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Jika Anda menafsirkan instruksi ini seperti komputer, sarapan membutuhkan waktu sekitar 30 menit untuk disiapkan. Durasinya adalah jumlah waktu tugas individual. Komputer memblokir untuk setiap pernyataan hingga semua pekerjaan selesai, dan kemudian melanjutkan ke pernyataan tugas berikutnya. Pendekatan ini dapat memakan waktu yang signifikan. Dalam contoh sarapan, metode komputer menciptakan sarapan yang tidak memuaskan. Tugas selanjutnya dalam daftar sinkron, seperti memanggang roti, jangan mulai sampai tugas sebelumnya selesai. Beberapa makanan menjadi dingin sebelum sarapan siap disajikan.
Jika Anda ingin komputer menjalankan instruksi secara asinkron, Anda harus menulis kode asinkron. Saat Anda menulis program klien, Anda ingin UI responsif terhadap input pengguna. Aplikasi Anda tidak boleh membekukan semua interaksi saat mengunduh data dari web. Ketika Anda menulis program server, Anda tidak ingin memblokir utas yang mungkin melayani permintaan lain. Menggunakan kode sinkron ketika ada alternatif asinkron dapat menghambat kemampuan Anda untuk meningkatkan skala dengan biaya yang lebih hemat. Anda membayar utas yang diblokir.
Aplikasi modern yang berhasil memerlukan kode asinkron. Tanpa dukungan bahasa, menulis kode asinkron memerlukan panggilan balik, peristiwa penyelesaian, atau cara lain yang mengaburkan niat asli kode. Keuntungan dari kode sinkron adalah tindakan langkah demi langkah yang memudahkan untuk memindai dan memahami. Model asinkron tradisional memaksa Anda untuk fokus pada sifat kode asinkron, bukan pada tindakan dasar kode.
Jangan blokir, tunggu saja
Kode sebelumnya menyoroti praktik pemrograman yang tidak menguntungkan: Menulis kode sinkron untuk melakukan operasi asinkron. Kode memblokir thread saat ini dari melakukan pekerjaan lain. Kode tidak akan mengganggu utas saat ada tugas yang sedang berjalan. Hasil dari model ini mirip dengan menatap pemangas roti setelah Anda memasukkan roti. Anda mengabaikan gangguan apa pun dan tidak memulai tugas lain sampai roti muncul. Anda tidak mengambil mentega dan selai keluar dari lemari es. Anda mungkin melewatkan melihat ketika api mulai menyala di atas kompor. Anda ingin memanggang roti sambil menangani masalah lain pada saat yang sama. Hal yang sama berlaku dengan kode Anda.
Anda dapat memulai dengan memperbarui kode sehingga thread tidak terblokir saat tugas sedang berjalan. Kata kunci Await menyediakan cara nonblokir untuk memulai tugas, lalu melanjutkan eksekusi saat tugas selesai. Versi asinkron sederhana dari kode sarapan terlihat seperti cuplikan berikut:
Module AsyncBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggs As Egg = Await FryEggsAsync(2)
Console.WriteLine("eggs are ready")
Dim hashBrown As HashBrown = Await FryHashBrownsAsync(3)
Console.WriteLine("hash browns are ready")
Dim toast As Toast = Await ToastBreadAsync(2)
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
Kode memperbarui badan metode asli dari FryEggs, FryHashBrowns, dan ToastBread untuk mengembalikan objek Task(Of Egg), Task(Of HashBrown), dan Task(Of Toast) masing-masing. Nama metode yang diperbarui mencakup akhiran "Asinkron": FryEggsAsync, , FryHashBrownsAsyncdan ToastBreadAsync. Fungsi Main mengembalikan objek Task, meskipun tidak memiliki ekspresi Return, yang memang dirancang begitu.
Nota
Kode yang diperbarui belum memanfaatkan fitur utama pemrograman asinkron, yang dapat mengakibatkan waktu penyelesaian yang lebih singkat. Kode memproses tugas dalam jumlah waktu yang kira-kira sama dengan versi sinkron awal. Untuk implementasi metode lengkap, lihat versi akhir kode nanti di artikel ini.
Mari kita terapkan contoh sarapan ke kode yang diperbarui. Utas tidak memblokir saat telur atau hash brown sedang dimasak, tetapi kode juga tidak memulai tugas lain sampai pekerjaan saat ini selesai. Anda masih menaruh roti di pemanggang roti dan menatap pemanggang roti sampai roti muncul, tetapi Anda sekarang dapat merespons gangguan. Di restoran tempat beberapa pesanan ditempatkan, juru masak dapat memulai pesanan baru sementara yang lain sudah memasak.
Dalam kode yang diperbarui, utas yang mengerjakan sarapan tidak diblokir saat menunggu tugas yang sudah dimulai tetapi belum selesai. Untuk beberapa aplikasi, perubahan ini adalah yang Anda butuhkan. Anda dapat mengaktifkan aplikasi untuk mendukung interaksi pengguna saat unduhan data dari web. Dalam skenario lain, Anda mungkin ingin memulai tugas lain sambil menunggu tugas sebelumnya selesai.
Mulai tugas secara bersamaan
Untuk sebagian besar operasi, Anda ingin segera memulai beberapa tugas independen. Setelah setiap tugas selesai, Anda memulai pekerjaan lain yang siap dimulai. Ketika Anda menerapkan metodologi ini ke contoh sarapan, Anda dapat menyiapkan sarapan lebih cepat. Anda juga menyiapkan semuanya dekat dengan waktu yang sama, sehingga Anda dapat menikmati sarapan panas.
Kelas Task dan jenis terkait adalah kelas yang dapat Anda gunakan untuk menerapkan gaya penalaran ini ke tugas yang sedang berlangsung. Pendekatan ini memungkinkan Anda menulis kode yang lebih mirip dengan cara Anda membuat sarapan dalam kehidupan nyata. Anda mulai memasak telur, hash brown, dan roti bakar pada saat yang sama. Karena setiap item makanan memerlukan tindakan, Anda memusatkan perhatian Anda pada tugas tersebut, menyelesaikan tindakan tersebut, dan kemudian menunggu sesuatu yang membutuhkan perhatian Anda.
Dalam kode, Anda memulai tugas dan berpegang pada objek Task yang mewakili pekerjaan. Anda menggunakan metode Await pada tugas untuk menunda tindakan pada pekerjaan hingga hasilnya siap.
Terapkan perubahan ini pada kode sarapan. Langkah pertama adalah menyimpan tugas untuk operasi ketika tugas dimulai, daripada menggunakan ekspresi Await:
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")
Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)
Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")
Console.WriteLine("Breakfast is ready!")
Revisi ini tidak membantu menyiapkan sarapan Anda lebih cepat. Ekspresi Await diterapkan ke semua tugas segera setelah dimulai. Langkah selanjutnya adalah memindahkan ekspresi Await untuk hash browns dan telur ke bagian akhir dari metode, sebelum Anda menyajikan sarapan.
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)
Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")
Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")
Console.WriteLine("Breakfast is ready!")
Anda sekarang memiliki sarapan yang disiapkan secara asinkron yang membutuhkan waktu sekitar 20 menit untuk disiapkan. Total waktu memasak berkurang karena beberapa tugas berjalan bersamaan.
Pembaruan kode meningkatkan proses persiapan dengan mengurangi waktu memasak, tetapi mereka memperkenalkan regresi dengan membakar telur dan hash browns. Anda memulai semua tugas asinkron secara bersamaan. Anda hanya menunggu hasil dari setiap tugas saat membutuhkannya. Kode mungkin mirip dengan program dalam aplikasi web yang membuat permintaan ke layanan mikro yang berbeda dan kemudian menggabungkan hasilnya ke dalam satu halaman. Anda segera membuat semua permintaan, lalu menerapkan ekspresi Await pada semua tugas tersebut dan menyusun halaman web.
Dukungan komposisi melalui tugas
Revisi kode sebelumnya membantu menyiapkan semuanya untuk sarapan pada saat yang sama, kecuali roti panggang. Proses membuat roti panggang adalah komposisi dari operasi asinkron (memanggang roti) dengan operasi sinkron (menyebarkan mentega dan selai pada roti panggang). Contoh ini menggambarkan konsep penting tentang pemrograman asinkron:
Penting
Komposisi operasi asinkron diikuti oleh pekerjaan sinkron adalah operasi asinkron. Dinyatakan dengan cara lain, jika ada bagian dari operasi yang asinkron, seluruh operasi adalah asinkron.
Dalam pembaruan sebelumnya, Anda mempelajari cara menggunakan objek Task atau Task<TResult> untuk menyimpan tugas yang sedang berjalan. Anda menunggu setiap tugas sebelum menggunakan hasilnya. Langkah selanjutnya adalah membuat metode yang mewakili kombinasi pekerjaan lain. Sebelum menyajikan sarapan, Anda perlu menyelesaikan tugas memanggang roti terlebih dahulu sebelum menyebarkan mentega dan selai.
Anda dapat mewakili pekerjaan ini dengan kode berikut:
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
Metode MakeToastWithButterAndJamAsync memiliki pengubah Async dalam tanda tangannya yang memberi sinyal ke pengompilasi bahwa metode berisi ekspresi Await dan berisi operasi asinkron. Metode ini mewakili tugas yang memanggang roti, kemudian menyebarkan mentega dan selai. Metode mengembalikan objek Task<TResult> yang mewakili komposisi tiga operasi.
Blok utama kode yang direvisi sekarang terlihat seperti ini:
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggsTask = FryEggsAsync(2)
Dim hashBrownTask = FryHashBrownsAsync(3)
Dim toastTask = MakeToastWithButterAndJamAsync(2)
Dim eggs = Await eggsTask
Console.WriteLine("eggs are ready")
Dim hashBrown = Await hashBrownTask
Console.WriteLine("hash browns are ready")
Dim toast = Await toastTask
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Perubahan kode ini menggambarkan teknik penting untuk bekerja dengan kode asinkron. Anda menyusun tugas dengan memisahkan operasi ke dalam metode baru yang mengembalikan tugas. Anda dapat memilih kapan harus menunggu pada tugas tersebut. Anda dapat memulai tugas lain secara bersamaan.
Mengatasi pengecualian asinkron
Hingga saat ini, kode Anda secara implisit mengasumsikan semua tugas berhasil diselesaikan. Metode asinkron melemparkan pengecualian, sama seperti rekan-rekan sinkron mereka. Tujuan untuk dukungan asinkron untuk pengecualian dan penanganan kesalahan sama dengan untuk dukungan asinkron secara umum. Praktik terbaik adalah menulis kode yang berbunyi seperti serangkaian pernyataan sinkron. Tugas menghasilkan pengecualian ketika tidak dapat diselesaikan dengan sukses. Kode klien dapat menangkap pengecualian tersebut saat ekspresi Await diterapkan ke tugas yang dimulai.
Dalam contoh sarapan, misalkan pemanggang roti menangkap api saat memanggang roti. Anda dapat mensimulasikan masalah tersebut dengan memodifikasi metode ToastBreadAsync agar sesuai dengan kode berikut:
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(2000)
Console.WriteLine("Fire! Toast is ruined!")
Throw New InvalidOperationException("The toaster is on fire")
Await Task.Delay(1000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Nota
Saat mengkompilasi kode ini, Anda akan melihat peringatan tentang kode yang tidak dapat dijangkau. Kesalahan ini didesain. Setelah toaster terbakar, operasi tidak berjalan normal dan kode mengembalikan kesalahan.
Setelah Anda membuat perubahan kode, jalankan aplikasi dan periksa output:
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 hash brown patties in the pan
Cooking first side of hash browns...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a hash brown patty
Flipping a hash brown patty
Flipping a hash brown patty
Cooking the second side of hash browns...
Cracking 2 eggs
Cooking the eggs ...
Put hash browns on plate
Put eggs on plate
Eggs are ready
Hash browns are ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.vb:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.vb:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.vb:line 24
at AsyncBreakfast.Program.<Main>(String[] args)
Perhatikan bahwa cukup banyak tugas selesai antara waktu ketika toaster terbakar dan sistem mendeteksi pengecualian. Ketika tugas yang berjalan secara asinkron melempar pengecualian, tugas tersebut rusak. Objek Task memuat pengecualian yang dilemparkan pada properti Task.Exception. Tugas yang mengalami kesalahan lempar pengecualian ketika ekspresi Await diterapkan ke tugas.
Ada dua mekanisme penting untuk dipahami tentang proses ini:
- Bagaimana pengecualian disimpan dalam tugas yang rusak.
- Bagaimana pengecualian dibongkar dan dilemparkan kembali ketika kode menunggu (
Await) pada tugas yang mengalami kesalahan.
Saat kode yang berjalan secara asinkron melemparkan pengecualian, pengecualian disimpan di objek Task. Properti Task.Exception adalah objek AggregateException karena lebih dari satu pengecualian mungkin dilempar selama proses asinkron. Pengecualian apa pun yang dilemparkan ditambahkan ke koleksi AggregateException.InnerExceptions. Jika properti Exception bernilai null, maka objek baru AggregateException akan dibuat dan pengecualian yang dilemparkan menjadi item pertama dalam koleksi.
Skenario paling umum untuk tugas yang salah adalah bahwa properti Exception berisi persis satu pengecualian. Ketika kode Anda menunggu pada tugas yang gagal, kode akan melempar ulang pengecualian AggregateException.InnerExceptions pertama dalam koleksi. Hasil ini adalah alasan mengapa output dari contoh menunjukkan objek InvalidOperationException daripada objek AggregateException. Mengekstrak pengecualian internal pertama membuat bekerja dengan metode asinkron semirip mungkin dengan bekerja menggunakan metode sinkron. Anda dapat memeriksa properti Exception dalam kode Anda saat skenario Anda mungkin menghasilkan beberapa pengecualian.
Tip
Praktik yang direkomendasikan adalah agar setiap pengecualian validasi argumen muncul secara sinkron dari metode yang mengembalikan tugas. Untuk informasi dan contoh selengkapnya, lihat Pengecualian dalam metode pengembalian tugas.
Sebelum Melanjutkan ke bagian berikutnya, komentari dua pernyataan berikut dalam metode ToastBreadAsync Anda. Anda tidak ingin memulai kebakaran lain:
' Console.WriteLine("Fire! Toast is ruined!")
' Throw New InvalidOperationException("The toaster is on fire")
Gunakan ekspresi 'await' pada tugas secara efisien
Anda dapat meningkatkan rangkaian ekspresi Await di akhir kode sebelumnya dengan menggunakan metode kelas Task. Satu API adalah metode WhenAll, yang mengembalikan objek Task yang selesai ketika semua tugas dalam daftar argumennya selesai. Kode berikut menunjukkan metode ini:
Await Task.WhenAll(eggsTask, hashBrownTask, toastTask)
Console.WriteLine("Eggs are ready")
Console.WriteLine("Hash browns are ready")
Console.WriteLine("Toast is ready")
Console.WriteLine("Breakfast is ready!")
Opsi lain adalah menggunakan metode WhenAny, yang mengembalikan objek Task(Of Task) yang selesai ketika salah satu argumennya selesai. Anda bisa menunggu tugas yang dikembalikan karena Anda tahu tugas selesai. Kode berikut menunjukkan bagaimana Anda dapat menggunakan metode WhenAny untuk menunggu tugas pertama selesai lalu memproses hasilnya. Setelah Anda memproses hasil dari tugas yang selesai, Anda menghapus tugas yang selesai dari daftar tugas yang diteruskan ke metode WhenAny.
Module ConcurrentBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)
Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
While breakfastTasks.Count > 0
Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
If finishedTask Is eggsTask Then
Console.WriteLine("eggs are ready")
ElseIf finishedTask Is hashBrownTask Then
Console.WriteLine("hash browns are ready")
ElseIf finishedTask Is toastTask Then
Console.WriteLine("toast is ready")
End If
Await finishedTask
breakfastTasks.Remove(finishedTask)
End While
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
Di dekat akhir cuplikan kode, perhatikan ekspresi Await finishedTask. Baris ini penting karena Task.WhenAny mengembalikan tugas - pembungkus Task(Of Task) yang berisi tugas yang telah selesai. Ketika Anda Await Task.WhenAny, Anda menunggu tugas pembungkus selesai, dan hasilnya adalah tugas aktual yang selesai terlebih dahulu. Namun, untuk mengambil hasil tugas tersebut atau memastikan pengecualian apa pun dilemparkan dengan benar, Anda harus Await menyelesaikan tugas itu sendiri (disimpan dalam finishedTask). Meskipun Anda tahu tugas telah selesai, menunggunya lagi memungkinkan Anda untuk mengakses hasilnya atau menangani pengecualian apa pun yang mungkin menyebabkan kesalahan.
Meninjau kode akhir
Berikut tampilan versi akhir kode:
Module ConcurrentBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)
Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
While breakfastTasks.Count > 0
Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
If finishedTask Is eggsTask Then
Console.WriteLine("eggs are ready")
ElseIf finishedTask Is hashBrownTask Then
Console.WriteLine("hash browns are ready")
ElseIf finishedTask Is toastTask Then
Console.WriteLine("toast is ready")
End If
Await finishedTask
breakfastTasks.Remove(finishedTask)
End While
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
Kode menyelesaikan tugas sarapan asinkron dalam waktu sekitar 15 menit. Total waktu berkurang karena beberapa tugas berjalan bersamaan. Kode secara bersamaan memantau beberapa tugas dan mengambil tindakan hanya sesuai kebutuhan.
Kode akhir tidak sinkron. Ini lebih akurat mencerminkan bagaimana seseorang dapat memasak sarapan. Bandingkan kode akhir dengan sampel kode pertama dalam artikel. Tindakan inti masih jelas dengan membaca kode. Anda dapat membaca kode akhir dengan cara yang sama seperti Anda membaca daftar instruksi untuk membuat sarapan, seperti yang ditunjukkan di awal artikel. Fitur bahasa untuk kata kunci Async dan Await menyediakan terjemahan yang dibuat setiap orang untuk mengikuti instruksi tertulis: Mulai tugas yang Anda bisa dan jangan blokir saat menunggu tugas selesai.
Asinkron/tunggu vs LanjutkanWith
Kata kunci Async dan Await memberikan penyederhanaan sintaks daripada penggunaan ContinueWith secara langsung. Meskipun Async/Await dan ContinueWith memiliki semantik serupa untuk menangani operasi asinkron, pengompilasi tidak selalu menerjemahkan Await ekspresi langsung ke dalam ContinueWith panggilan metode. Sebaliknya, kompilator menghasilkan kode komputer status yang dioptimalkan yang menyediakan perilaku logis yang sama. Transformasi ini memberikan manfaat keterbacaan dan pemeliharaan yang signifikan, terutama ketika menautkan beberapa operasi asinkron.
Pertimbangkan skenario di mana Anda perlu melakukan beberapa operasi asinkron berurutan. Berikut adalah bagaimana tampilan logika yang sama saat diimplementasikan dengan ContinueWith dibandingkan dengan Async/Await.
Menggunakan ContinueWith
Dengan ContinueWith, setiap langkah dalam urutan operasi asinkron memerlukan kelanjutan berlapis:
' Using ContinueWith - demonstrates the complexity when chaining operations
Function MakeBreakfastWithContinueWith() As Task
Return StartCookingEggsAsync() _
.ContinueWith(Function(eggsTask)
Dim eggs = eggsTask.Result
Console.WriteLine("Eggs ready, starting bacon...")
Return StartCookingBaconAsync()
End Function) _
.Unwrap() _
.ContinueWith(Function(baconTask)
Dim bacon = baconTask.Result
Console.WriteLine("Bacon ready, starting toast...")
Return StartToastingBreadAsync()
End Function) _
.Unwrap() _
.ContinueWith(Function(toastTask)
Dim toast = toastTask.Result
Console.WriteLine("Toast ready, applying butter...")
Return ApplyButterAsync(toast)
End Function) _
.Unwrap() _
.ContinueWith(Function(butteredToastTask)
Dim butteredToast = butteredToastTask.Result
Console.WriteLine("Butter applied, applying jam...")
Return ApplyJamAsync(butteredToast)
End Function) _
.Unwrap() _
.ContinueWith(Sub(finalToastTask)
Dim finalToast = finalToastTask.Result
Console.WriteLine("Breakfast completed with ContinueWith!")
End Sub)
End Function
Menggunakan Asinkron/Tunggu
Urutan operasi yang sama menggunakan Async/Await terasa jauh lebih alami.
' Using Async/Await - much cleaner and easier to read
Async Function MakeBreakfastWithAsyncAwait() As Task
Dim eggs = Await StartCookingEggsAsync()
Console.WriteLine("Eggs ready, starting bacon...")
Dim bacon = Await StartCookingBaconAsync()
Console.WriteLine("Bacon ready, starting toast...")
Dim toast = Await StartToastingBreadAsync()
Console.WriteLine("Toast ready, applying butter...")
Dim butteredToast = Await ApplyButterAsync(toast)
Console.WriteLine("Butter applied, applying jam...")
Dim finalToast = Await ApplyJamAsync(butteredToast)
Console.WriteLine("Breakfast completed with Async/Await!")
End Function
Mengapa Asinkron/Menunggu lebih disukai
Pendekatan ini Async/Await menawarkan beberapa keuntungan:
- Keterbacaan: Kode membaca seperti kode sinkron, sehingga lebih mudah untuk memahami alur operasi.
- Ketahanan: Menambahkan atau menghapus langkah-langkah dalam urutan memerlukan perubahan kode minimal.
-
Penanganan kesalahan: Penanganan pengecualian dengan
Try/Catchblok berfungsi secara alami, sedangkanContinueWithmemerlukan penanganan tugas yang rusak dengan hati-hati. -
Debugging: Tumpukan panggilan dan pengalaman debugger jauh lebih baik dengan
Async/Await. -
Performa: Optimalisasi kompilator untuk
Async/Awaitlebih canggih daripada rangkaian manual.ContinueWith
Manfaatnya menjadi lebih jelas ketika jumlah operasi berantai meningkat. Meskipun satu kelanjutan mungkin dapat dikelola dengan ContinueWith, urutan 3-4 atau lebih operasi asinkron dengan cepat menjadi sulit dibaca dan dipertahankan. Pola ini, yang dikenal sebagai "monadic do-notation" dalam pemrograman fungsional, memungkinkan Anda menyusun beberapa operasi asinkron secara berurutan dan dapat dibaca.