Setelah berurusan dengan pola async / await C # untuk sementara waktu sekarang, saya tiba-tiba menyadari bahwa saya tidak benar-benar tahu bagaimana menjelaskan apa yang terjadi dalam kode berikut:
async void MyThread()
{
while (!_quit)
{
await GetWorkAsync();
}
}
GetWorkAsync()
diasumsikan mengembalikan yang ditunggu-tunggu Task
yang mungkin atau mungkin tidak menyebabkan ulir ketika kelanjutan dijalankan.
Saya tidak akan bingung jika penantian tidak berada di dalam satu lingkaran. Saya secara alami berharap bahwa sisa metode (yaitu kelanjutan) berpotensi mengeksekusi di utas lain, yang baik-baik saja.
Namun, di dalam satu lingkaran, konsep "sisa metode" menjadi agak berkabut bagi saya.
Apa yang terjadi pada "sisa loop" jika utas diaktifkan kelanjutan vs jika tidak diaktifkan? Di thread mana iterasi loop berikutnya dieksekusi?
Pengamatan saya menunjukkan (tidak diverifikasi secara meyakinkan) bahwa setiap iterasi dimulai pada utas yang sama (yang asli) sementara kelanjutan dijalankan pada yang lain. Bisakah ini benar-benar terjadi? Jika ya, apakah ini suatu tingkat paralelisme tak terduga yang perlu dipertanggungjawabkan terkait keamanan benang metode GetWorkAsync?
UPDATE: Pertanyaan saya bukan duplikat, seperti yang disarankan oleh beberapa orang. The while (!_quit) { ... }
Pola Kode hanyalah penyederhanaan kode saya yang sebenarnya. Pada kenyataannya, utas saya adalah loop yang berumur panjang yang memproses antrian input item pekerjaan secara berkala (setiap 5 detik secara default). Pemeriksaan kondisi berhenti aktual juga bukan pemeriksaan lapangan sederhana seperti yang disarankan oleh kode sampel, melainkan pemeriksaan pegangan acara.
Jawaban:
Anda sebenarnya bisa memeriksanya di Try Roslyn . Metode menunggu Anda akan ditulis ulang
void IAsyncStateMachine.MoveNext()
pada kelas async yang dihasilkan.Yang akan Anda lihat adalah sesuatu seperti ini:
Pada dasarnya, tidak masalah Anda berada di thread mana; mesin keadaan dapat melanjutkan dengan benar dengan mengganti loop Anda dengan struktur if / goto yang setara.
Karena itu, metode async tidak harus dijalankan pada utas yang berbeda. Lihat penjelasan Eric Lippert "Ini bukan sihir" untuk menjelaskan bagaimana Anda dapat bekerja
async/await
hanya pada satu utas.sumber
Pertama, Servy telah menulis beberapa kode dalam jawaban untuk pertanyaan serupa, yang menjadi dasar jawaban ini:
/programming/22049339/how-to-create-a-cancellable-task-loop
Jawaban Servy mencakup
ContinueWith()
loop serupa menggunakan konstruksi TPL tanpa penggunaan eksplisitasync
danawait
kata kunci; jadi untuk menjawab pertanyaan Anda, perhatikan seperti apa kode Anda saat loop Anda tidak digunakanContinueWith()
Ini membutuhkan waktu untuk membungkus kepala Anda, tetapi dalam ringkasan:
continuation
mewakili penutupan untuk "iterasi saat ini"previous
mewakili keadaan yangTask
berisi "iterasi sebelumnya" (yaitu ia tahu kapan 'iterasi' selesai dan digunakan untuk memulai yang berikutnya ..)GetWorkAsync()
mengembalikan aTask
, itu berartiContinueWith(_ => GetWorkAsync())
akan mengembalikanTask<Task>
maka karenanya panggilanUnwrap()
untuk mendapatkan 'tugas batin' (yaitu hasil aktual dariGetWorkAsync()
).Begitu:
Task.FromResult(_quit)
- negaranya dimulai sebagaiTask.Completed == true
.continuation
dijalankan untuk pertama kalinya menggunakanprevious.ContinueWith(continuation)
continuation
update penutupanprevious
untuk mencerminkan keadaan selesainya_ => GetWorkAsync()
_ => GetWorkAsync()
selesai, ia "melanjutkan"_previous.ContinueWith(continuation)
- yaitu memanggilcontinuation
lambda lagiprevious
telah diperbarui dengan keadaan_ => GetWorkAsync()
sehinggacontinuation
lambda dipanggil ketikaGetWorkAsync()
kembali.The
continuation
lambda selalu memeriksa keadaan_quit
begitu, jika_quit == false
kemudian tidak ada lagi lanjutan, danTaskCompletionSource
akan diatur ke nilai_quit
, dan semuanya selesai.Adapun pengamatan Anda tentang kelanjutan dieksekusi di utas yang berbeda, itu bukan sesuatu yang kata kunci
async
/await
akan lakukan untuk Anda, sesuai blog ini "Tugas (masih) bukan utas dan async tidak paralel" . - https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/Saya sarankan memang perlu melihat lebih dekat pada
GetWorkAsync()
metode Anda sehubungan dengan threading dan keamanan benang. Jika diagnostik Anda mengungkapkan bahwa itu telah dieksekusi pada utas yang berbeda sebagai konsekuensi dari kode async / wait Anda yang diulang, maka sesuatu di dalam atau yang terkait dengan metode itu harus menyebabkan utas baru dibuat di tempat lain. (Jika ini tidak terduga, mungkin ada suatu.ConfigureAwait
tempat?)sumber
SynchronizationContext
- yang tentu saja penting karena.ContinueWith()
menggunakan SynchronizationContext untuk mengirim kelanjutan; itu memang akan menjelaskan perilaku yang Anda lihat jikaawait
dipanggil pada utas ThreadPool atau utas ASP.NET. Kelanjutan tentu dapat dikirim ke utas berbeda dalam kasus-kasus tersebut. Di sisi lain, memanggilawait
konteks single-threaded seperti WPF Dispatcher atau konteks Winforms harus cukup untuk memastikan kelanjutan terjadi pada aslinya. utas