Saya memiliki public async void Foo()
metode yang ingin saya panggil dari metode sinkron. Sejauh ini semua yang saya lihat dari dokumentasi MSDN adalah memanggil metode async melalui metode async, tetapi seluruh program saya tidak dibangun dengan metode async.
Apakah ini mungkin?
Berikut adalah salah satu contoh memanggil metode ini dari metode asinkron: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Sekarang saya sedang mencari cara memanggil metode async ini dari metode sinkronisasi.
c#
async-await
Menara
sumber
sumber
async void Foo()
metode Anda tidak mengembalikannya,Task
itu berarti seorang penelepon tidak dapat mengetahui kapan itu selesai, ia harus kembaliTask
sebagai gantinya.Jawaban:
Pemrograman asinkron tidak "tumbuh" melalui basis kode. Ini telah dibandingkan dengan virus zombie . Solusi terbaik adalah membiarkannya tumbuh, tetapi terkadang itu tidak mungkin.
Saya telah menulis beberapa jenis di pustaka Nito.AsyncEx saya untuk berurusan dengan basis kode asinkron parsial. Tidak ada solusi yang berfungsi di setiap situasi.
Solusi A
Jika Anda memiliki metode asinkron sederhana yang tidak perlu disinkronkan kembali ke konteksnya, maka Anda dapat menggunakan
Task.WaitAndUnwrapException
:Anda tidak ingin menggunakan
Task.Wait
atauTask.Result
karena mereka memasukkan pengecualianAggregateException
.Solusi ini hanya sesuai jika
MyAsyncMethod
tidak disinkronkan kembali ke konteksnya. Dengan kata lain, setiapawait
diMyAsyncMethod
harus berakhir denganConfigureAwait(false)
. Ini berarti tidak dapat memperbarui elemen UI apa pun atau mengakses konteks permintaan ASP.NET.Solusi B
Jika
MyAsyncMethod
memang perlu disinkronkan kembali ke konteksnya, maka Anda mungkin dapat menggunakanAsyncContext.RunTask
untuk memberikan konteks bersarang:* Pembaruan 4/14/2014: Dalam versi perpustakaan yang lebih baru, API adalah sebagai berikut:
(Tidak apa-apa untuk digunakan
Task.Result
dalam contoh ini karenaRunTask
akan menyebarkanTask
pengecualian).Alasan Anda mungkin perlu
AsyncContext.RunTask
bukannyaTask.WaitAndUnwrapException
karena kebuntuan yang agak halus yang terjadi pada WinForms / WPF / SL / ASP.NET:Task
.Task
.async
Metode menggunakanawait
tanpaConfigureAwait
.Task
dapat menyelesaikan dalam situasi ini karena hanya selesai ketikaasync
metode selesai; yangasync
metode dapat tidak lengkap karena mencoba untuk menjadwalkan kelanjutan kepadaSynchronizationContext
, dan WinForms / WPF / SL / ASP.NET tidak akan mengizinkan kelanjutan untuk menjalankan karena metode sinkron sudah berjalan dalam konteks itu.Ini adalah salah satu alasan mengapa sebaiknya menggunakan sebanyak mungkin metode
ConfigureAwait(false)
dalam setiapasync
metode.Solusi C
AsyncContext.RunTask
tidak akan berfungsi di setiap skenario. Misalnya, jikaasync
metode menunggu sesuatu yang memerlukan acara UI untuk menyelesaikan, maka Anda akan menemui jalan buntu bahkan dengan konteks bersarang. Jika demikian, Anda bisa memulaiasync
metode di kumpulan utas:Namun, solusi ini membutuhkan
MyAsyncMethod
yang akan bekerja dalam konteks thread pool. Jadi itu tidak dapat memperbarui elemen UI atau mengakses konteks permintaan ASP.NET. Dan dalam hal ini, Anda mungkin juga menambahkanConfigureAwait(false)
untuk nyaawait
pernyataan, dan menggunakan solusi A.Pembaruan, 2019-05-01: "Praktik terburuk" saat ini ada di artikel MSDN di sini .
sumber
WaitAndUnwrapException
adalah metode saya sendiri dari pustaka AsyncEx saya . Lib .NET resmi tidak memberikan banyak bantuan untuk mencampur sinkronisasi dan kode async (dan secara umum, Anda seharusnya tidak melakukannya!). Saya menunggu .NET 4.5 RTW dan laptop non-XP baru sebelum memperbarui AsyncEx untuk berjalan pada 4,5 (saat ini saya tidak dapat mengembangkan untuk 4,5 karena saya terjebak di XP selama beberapa minggu lagi).AsyncContext
sekarang memilikiRun
metode yang mengambil ekspresi lambda, jadi Anda harus menggunakanvar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
metode. Hal terdekat yang bisa saya temukan adalahRun
, tetapi itu tidak memilikiResult
properti.var result = AsyncContext.Run(MyAsyncMethod);
Menambahkan solusi yang akhirnya menyelesaikan masalah saya, semoga menghemat waktu seseorang.
Pertama baca beberapa artikel dari Stephen Cleary :
Dari "dua praktik terbaik" di "Jangan Blokir pada Kode Async", yang pertama tidak bekerja untuk saya dan yang kedua tidak berlaku (pada dasarnya jika saya bisa menggunakan
await
, saya lakukan!).Jadi di sini adalah solusi saya: bungkus panggilan di dalam
Task.Run<>(async () => await FunctionAsync());
dan mudah-mudahan tidak ada jalan buntu lagi.Ini kode saya:
sumber
Task.Run()
ini bukan praktik terbaik dalam kode async. Tetapi, sekali lagi, apa jawaban untuk pertanyaan awal? Jangan pernah panggil metode async secara sinkron? Kami berharap, tetapi di dunia nyata, kadang-kadang kita harus melakukannya.Parallel.ForEach
pelecehan tidak akan berpengaruh pada 'dunia nyata' dan pada akhirnya ia menurunkan server. Kode ini OK untuk aplikasi Konsol tetapi seperti yang dikatakan @ChrisPratt, tidak boleh digunakan di Aplikasi Web. Mungkin berfungsi "sekarang" tetapi tidak dapat diskalakan.Microsoft membangun kelas AsyncHelper (internal) untuk menjalankan Async sebagai Sinkronisasi. Sumbernya terlihat seperti:
Kelas dasar Microsoft.AspNet.Identity hanya memiliki metode Async dan untuk memanggil mereka sebagai Sinkronisasi ada kelas dengan metode ekstensi yang terlihat seperti (contoh penggunaan):
Bagi mereka yang peduli tentang ketentuan kode lisensi, berikut ini tautan ke kode yang sangat mirip (hanya menambahkan dukungan untuk kultur di utas) yang memiliki komentar untuk menunjukkan bahwa itu adalah MIT yang dilisensikan oleh Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
sumber
await
panggilan saya denganConfigureAwait(false)
. Saya mencoba menggunakanAsyncHelper.RunSync
untuk memanggil fungsi async dariApplication_Start()
fungsi di Global.asax dan sepertinya berfungsi. Apakah ini berarti bahwaAsyncHelper.RunSync
andal tidak rentan terhadap masalah kebuntuan "marshal kembali ke konteks pemanggil" yang saya baca di tempat lain dalam posting ini?async Main sekarang merupakan bagian dari C # 7.2 dan dapat diaktifkan dalam pengaturan lanjutan bangunan proyek.
Untuk C # <7.2, cara yang benar adalah:
Anda akan melihat ini digunakan dalam banyak dokumentasi Microsoft, misalnya: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- topik-langganan
sumber
MainAsync().Wait()
?Anda membaca kata kunci 'tunggu' sebagai "mulai tugas yang sudah berjalan lama ini, lalu kembalikan kontrol ke metode panggilan" Setelah tugas jangka panjang selesai, ia menjalankan kode setelahnya. Kode setelah menunggu mirip dengan apa yang dulu metode CallBack. Perbedaan besar menjadi aliran logis tidak terputus yang membuatnya lebih mudah untuk menulis dan membaca.
sumber
Wait
membungkus pengecualian dan memiliki kemungkinan kebuntuan.await
, itu akan dieksekusi secara sinkron. Setidaknya itu bekerja untuk saya (tanpa meneleponmyTask.Wait
). Sebenarnya, saya mendapat pengecualian ketika saya mencoba meneleponmyTask.RunSynchronously()
karena sudah dieksekusi!.Result
.Result
panggilan pada saat itu, sehingga tidak pernah sampai di sana. DanResult
tidak pernah berakhir, karena menunggu seseorang yang menunggu untukResult
berakhir, pada dasarnya: DSaya tidak 100% yakin, tapi saya percaya teknik yang dijelaskan dalam blog ini harus bekerja dalam banyak keadaan:
sumber
Namun, ada solusi bagus yang bekerja di (hampir: lihat komentar) setiap situasi: pompa pesan ad-hoc (SynchronizationContext).
Utas panggilan akan diblokir seperti yang diharapkan, sambil tetap memastikan bahwa semua kelanjutan yang dipanggil dari fungsi async tidak menemui jalan buntu karena mereka akan diarahkan ke Ad-hoc SynchronizationContext (pompa pesan) yang berjalan pada utas panggilan.
Kode pembantu pompa pesan ad-hoc:
Pemakaian:
Penjelasan lebih rinci tentang pompa async tersedia di sini .
sumber
Kepada siapa pun yang memperhatikan pertanyaan ini lagi ...
Jika Anda melihat
Microsoft.VisualStudio.Services.WebApi
ada kelas yang disebutTaskExtensions
. Di dalam kelas itu Anda akan melihat metode ekstensi statisTask.SyncResult()
, yang seperti benar-benar hanya memblokir utas sampai tugas kembali.Secara internal itu panggilan
task.GetAwaiter().GetResult()
yang sangat sederhana, namun itu kelebihan beban untuk bekerja padaasync
metode apa pun yang kembaliTask
,Task<T>
atauTask<HttpResponseMessage>
... gula sintaksis, sayang ... ayah punya gigi manis.Sepertinya
...GetAwaiter().GetResult()
ini adalah cara resmi-MS untuk mengeksekusi kode async dalam konteks pemblokiran. Tampaknya bekerja dengan sangat baik untuk use case saya.sumber
Atau gunakan ini:
sumber
Anda dapat memanggil metode asinkron apa pun dari kode sinkron, yaitu, sampai Anda perlu
await
menggunakannya, dalam hal ini mereka juga harus ditandaiasync
.Karena banyak orang menyarankan di sini, Anda bisa memanggil Tunggu () atau Hasil pada tugas yang dihasilkan dalam metode sinkron Anda, tetapi kemudian Anda berakhir dengan panggilan pemblokiran dalam metode itu, yang jenisnya mengalahkan tujuan async.
Jika Anda benar-benar tidak dapat membuat metode
async
Anda dan Anda tidak ingin mengunci metode sinkron, maka Anda harus menggunakan metode panggilan balik dengan meneruskannya sebagai parameter ke metode ContinueWith pada tugas.sumber
async
juga" menarik perhatian saya dari apa yang sebenarnya Anda katakan.Saya tahu saya sangat terlambat. Tapi kalau-kalau seseorang seperti saya ingin menyelesaikan ini dengan rapi, cara mudah, dan tanpa tergantung pada perpustakaan lain.
Saya menemukan potongan kode berikut dari Ryan
maka Anda bisa menyebutnya seperti ini
sumber
Setelah berjam-jam mencoba metode yang berbeda, dengan keberhasilan yang kurang lebih, inilah yang saya akhiri. Itu tidak berakhir dengan jalan buntu saat mendapatkan hasil dan juga mendapatkan dan melempar pengecualian asli dan bukan yang dibungkus.
sumber
Itu bisa dipanggil dari utas baru (BUKAN dari kumpulan utas!):
sumber
Metode windows async tersebut memiliki metode kecil yang bagus yang disebut AsTask (). Anda bisa menggunakan ini untuk meminta metode mengembalikan dirinya sebagai tugas sehingga Anda bisa memanggil Tunggu () secara manual.
Misalnya, pada aplikasi Windows Phone 8 Silverlight, Anda dapat melakukan hal berikut:
Semoga ini membantu!
sumber
Jika Anda ingin menjalankannya Sync
sumber
RunSynchronously()
hasil tugas panas ke sebuahInvalidOperationException
. Cobalah dengan kode ini:Task.Run(() => {}).RunSynchronously();
sumber