Saya punya 3 tugas:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Mereka semua perlu dijalankan sebelum kode saya dapat dilanjutkan dan saya juga membutuhkan hasil dari masing-masing. Tidak ada hasil yang memiliki kesamaan satu sama lain
Bagaimana cara saya menelepon dan menunggu 3 tugas selesai dan kemudian mendapatkan hasilnya?
c#
.net
async-await
task-parallel-library
.net-4.5
Ian Vink
sumber
sumber
Jawaban:
Setelah Anda gunakan
WhenAll
, Anda dapat menarik hasilnya secara individual denganawait
:Anda juga dapat menggunakan
Task.Result
(karena Anda tahu pada titik ini semuanya telah berhasil diselesaikan). Namun, saya sarankan menggunakanawait
karena itu jelas benar, sementaraResult
dapat menyebabkan masalah dalam skenario lain.sumber
WhenAll
seluruhnya dari ini; menunggu akan memastikan Anda tidak melewati 3 tugas nanti sampai semua tugas selesai.Task.WhenAll()
memungkinkan untuk menjalankan tugas dalam mode paralel . Saya tidak mengerti mengapa @Servy menyarankan untuk menghapusnya. TanpaWhenAll
mereka akan dijalankan satu per satucatTask
sudah berjalan pada saat ia kembaliFeedCat
. Jadi salah satu pendekatan akan berhasil - satu-satunya pertanyaan adalah apakah Anda inginawait
mereka satu per satu atau bersama-sama. Penanganan kesalahan sedikit berbeda - jika Anda menggunakanTask.WhenAll
, maka semuanya akan adaawait
, bahkan jika salah satu dari mereka gagal lebih awal.WhenAll
tidak berdampak pada saat operasi dijalankan, atau bagaimana mereka mengeksekusi. Ini hanya memiliki kemungkinan mempengaruhi bagaimana hasil diamati. Dalam kasus khusus ini, satu-satunya perbedaan adalah bahwa kesalahan dalam salah satu dari dua metode pertama akan menghasilkan pengecualian yang dilemparkan dalam tumpukan panggilan ini lebih awal dalam metode saya daripada Stephen (meskipun kesalahan yang sama akan selalu dilemparkan, jika ada ).Hanya
await
tiga tugas secara terpisah, setelah memulai semuanya.sumber
Task.WhenAll
perubahan apa pun tentang perilaku program, dengan cara apa pun yang dapat diobservasi secara harfiah. Ini adalah pemanggilan metode yang benar-benar berlebihan. Anda boleh menambahkannya, jika Anda suka, sebagai pilihan estetika, tetapi itu tidak mengubah apa yang dilakukan kode. Waktu eksekusi kode akan identik dengan atau tanpa pemanggilan metode itu (well, secara teknis akan ada biaya overhead yang sangat kecil untuk meneleponWhenAll
, tetapi ini harus diabaikan), hanya membuat versi itu sedikit lebih lama untuk dijalankan daripada versi ini.WhenAll
adalah perubahan estetika murni. Satu-satunya perbedaan yang dapat diamati dalam perilaku adalah apakah Anda menunggu tugas selanjutnya selesai jika tugas sebelumnya salah, yang biasanya tidak perlu dilakukan. Jika Anda tidak percaya dengan banyak penjelasan mengapa pernyataan Anda tidak benar, Anda dapat menjalankan sendiri kode dan melihat bahwa itu tidak benar.Jika Anda menggunakan C # 7, Anda dapat menggunakan metode pembungkus berguna seperti ini ...
... untuk mengaktifkan sintaks yang mudah digunakan seperti ini ketika Anda ingin menunggu beberapa tugas dengan jenis pengembalian yang berbeda. Anda harus membuat banyak kelebihan untuk sejumlah tugas yang berbeda untuk ditunggu, tentu saja.
Namun, lihat jawaban Marc Gravell untuk beberapa optimasi di sekitar ValueTask dan tugas yang sudah selesai jika Anda bermaksud untuk mengubah contoh ini menjadi sesuatu yang nyata.
sumber
Task.WhenAll()
tidak mengembalikan tuple. Satu sedang dibangun dariResult
sifat - sifat tugas yang diberikan setelah tugas dikembalikan olehTask.WhenAll()
selesai..Result
telepon sesuai alasan Stephen untuk menghindari orang lain mengabadikan praktik buruk dengan menyalin contoh Anda.Diberikan tiga tugas -
FeedCat()
,SellHouse()
danBuyCar()
, ada dua kasus menarik: apakah semuanya diselesaikan secara serempak (karena alasan tertentu, mungkin caching atau kesalahan), atau tidak.Katakanlah kita punya, dari pertanyaan:
Sekarang, pendekatan sederhana adalah:
tapi ... itu tidak nyaman untuk memproses hasil; kami biasanya ingin
await
itu:tetapi ini tidak banyak overhead dan mengalokasikan berbagai array (termasuk
params Task[]
array) dan daftar (secara internal). Ini bekerja, tetapi itu bukan IMO yang bagus. Dalam banyak hal lebih mudah untuk menggunakanasync
operasi dan hanyaawait
masing - masing pada gilirannya:Berlawanan dengan beberapa komentar di atas, menggunakan
await
bukannya tidakTask.WhenAll
membuat perbedaan pada bagaimana tugas dijalankan (secara bersamaan, secara berurutan, dll). Pada level tertinggi,Task.WhenAll
mendahului dukungan compiler yang baik untukasync
/await
, dan berguna ketika hal-hal itu tidak ada . Ini juga berguna ketika Anda memiliki array tugas yang sewenang-wenang, bukan 3 tugas yang tersembunyi.Tapi: kita masih memiliki masalah yang
async
/await
menghasilkan banyak compiler kebisingan untuk kelanjutan. Jika kemungkinan tugas tersebut benar-benar selesai secara sinkron, maka kami dapat mengoptimalkannya dengan membangun jalur sinkron dengan fallback asinkron:Pendekatan "jalur sinkronisasi dengan async fallback" ini semakin umum terutama dalam kode kinerja tinggi di mana penyelesaian sinkron relatif sering. Perhatikan itu tidak akan membantu sama sekali jika penyelesaiannya selalu benar-benar tidak sinkron.
Hal-hal tambahan yang berlaku di sini:
dengan C # baru-baru ini, pola umum untuk
async
metode fallback umumnya diterapkan sebagai fungsi lokal:lebih memilih
ValueTask<T>
untukTask<T>
jika ada kesempatan baik hal-hal yang pernah benar-benar serentak dengan banyak kembali nilai-nilai yang berbeda:jika mungkin, lebih memilih
IsCompletedSuccessfully
untukStatus == TaskStatus.RanToCompletion
; ini sekarang ada di .NET Core untukTask
, dan di mana-mana untukValueTask<T>
sumber
Task
ketika semuanya selesai tanpa menggunakan hasilnya.await
mendapatkan semantik pengecualian "lebih baik", dengan asumsi bahwa pengecualian jarang tetapi bermaknaAnda dapat menyimpannya dalam tugas, lalu menunggu semuanya:
sumber
var catTask = FeedCat()
melaksanakan fungsiFeedCat()
dan menyimpan hasilnya ke dalamcatTask
membuatawait Task.WhenAll()
bagian jenis berguna karena metode ini telah dijalankan ??Jika Anda mencoba untuk mencatat semua kesalahan, pastikan Anda tetap menggunakan Task.WhenAll baris dalam kode Anda, banyak komentar menyarankan bahwa Anda dapat menghapusnya dan menunggu tugas individu. Tugas. Ketika Semua sangat penting untuk penanganan kesalahan. Tanpa baris ini Anda berpotensi membiarkan kode Anda terbuka untuk pengecualian yang tidak teramati.
Bayangkan FeedCat melempar pengecualian dalam kode berikut:
Dalam hal ini Anda tidak akan pernah menunggu di houseTask atau carTask. Ada 3 skenario yang mungkin di sini:
SellHouse sudah berhasil diselesaikan ketika FeedCat gagal. Dalam hal ini Anda baik-baik saja.
SellHouse tidak lengkap dan gagal kecuali di beberapa titik. Pengecualian tidak diamati dan akan dipasang kembali pada utas finalizer.
SellHouse tidak lengkap dan berisi menunggu di dalamnya. Jika kode Anda berjalan di ASP.NET SellHouse akan gagal segera setelah beberapa menunggu akan selesai di dalamnya. Ini terjadi karena Anda pada dasarnya membuat panggilan api & lupa dan konteks sinkronisasi hilang begitu FeedCat gagal.
Berikut adalah kesalahan yang akan Anda dapatkan untuk kasus (3):
Untuk kasus (2) Anda akan mendapatkan kesalahan yang sama tetapi dengan jejak stack pengecualian asli.
Untuk .NET 4.0 dan yang lebih baru, Anda dapat menangkap pengecualian yang tidak teramati menggunakan TaskScheduler.UnobservedTaskException. Untuk .NET 4.5 dan kemudian pengecualian yang tidak diobservasi ditelan secara default untuk .NET 4.0 pengecualian yang tidak diobservasi akan membuat proses Anda macet.
Lebih detail di sini: Penanganan Pengecualian Tugas di .NET 4.5
sumber
Anda dapat menggunakan
Task.WhenAll
seperti yang disebutkan, atauTask.WaitAll
, tergantung pada apakah Anda ingin utas menunggu. Lihatlah tautan untuk penjelasan keduanya.WaitAll vs WhenAll
sumber
Gunakan
Task.WhenAll
dan kemudian tunggu hasilnya:sumber
Peringatan ke Depan
Hanya kepala cepat ke mereka yang mengunjungi ini dan utas serupa lainnya mencari cara untuk memparalelkan EntityFramework menggunakan async + menunggu + task tool-set : Namun, pola yang ditunjukkan di sini adalah suara, ketika menyangkut kepingan salju EF Anda tidak akan mencapai eksekusi paralel kecuali dan sampai Anda menggunakan instance db-context-instance yang terpisah di dalam masing-masing dan setiap panggilan * Async () yang terlibat.
Hal semacam ini diperlukan karena keterbatasan desain inheren konteks ef-db yang melarang menjalankan beberapa query secara paralel dalam instance konteks-ef yang sama.
Dengan memanfaatkan jawaban yang telah diberikan, ini adalah cara untuk memastikan bahwa Anda mengumpulkan semua nilai bahkan dalam kasus satu atau lebih tugas menghasilkan pengecualian:
Implementasi alternatif yang kurang lebih memiliki karakteristik kinerja yang sama dapat berupa:
sumber
jika Anda ingin mengakses Cat, lakukan ini:
Ini sangat mudah dilakukan dan sangat berguna untuk digunakan, tidak perlu mencari solusi yang kompleks.
sumber
dynamic
adalah iblis. Ini untuk interop COM yang rumit dan semacamnya, dan tidak boleh digunakan dalam situasi apa pun di mana itu tidak benar-benar diperlukan. Terutama jika Anda peduli dengan kinerja. Atau ketik keamanan. Atau refactoring. Atau debugging.