Kemarin saya memberikan ceramah tentang fitur C # "async" yang baru, khususnya menyelidiki seperti apa kode yang dihasilkan, dan the GetAwaiter()
/ BeginAwait()
/ EndAwait()
panggilan.
Kami melihat secara rinci mesin negara yang dihasilkan oleh kompiler C #, dan ada dua aspek yang tidak dapat kami pahami:
- Mengapa kelas yang dihasilkan berisi
Dispose()
metode dan$__disposing
variabel, yang sepertinya tidak pernah digunakan (dan kelas tidak mengimplementasikanIDisposable
). - Mengapa
state
variabel internal diatur ke 0 sebelum panggilan apa punEndAwait()
, ketika 0 biasanya muncul berarti "ini adalah titik masuk awal".
Saya menduga poin pertama dapat dijawab dengan melakukan sesuatu yang lebih menarik dalam metode async, walaupun jika ada yang punya informasi lebih lanjut saya akan senang mendengarnya. Pertanyaan ini lebih banyak tentang poin kedua.
Berikut ini contoh kode sampel yang sangat sederhana:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... dan inilah kode yang dihasilkan untuk MoveNext()
metode yang mengimplementasikan mesin negara. Ini disalin langsung dari Reflektor - Saya belum memperbaiki nama variabel yang tidak dapat diucapkan:
public void MoveNext()
{
try
{
this.$__doFinallyBodies = true;
switch (this.<>1__state)
{
case 1:
break;
case 2:
goto Label_00DA;
case -1:
return;
default:
this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
break;
}
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
this.<>1__state = 2;
this.$__doFinallyBodies = false;
if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
Label_00DA:
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
this.<>1__state = -1;
this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
}
catch (Exception exception)
{
this.<>1__state = -1;
this.$builder.SetException(exception);
}
}
Itu panjang, tetapi kalimat penting untuk pertanyaan ini adalah:
// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
Dalam kedua kasus keadaan diubah lagi setelah itu sebelum berikutnya jelas diamati ... jadi mengapa set ke 0 sama sekali? Jika MoveNext()
dipanggil lagi pada titik ini (baik secara langsung atau melalui Dispose
) itu akan secara efektif memulai metode async lagi, yang akan sepenuhnya tidak sesuai sejauh yang saya tahu ... jika dan MoveNext()
tidak dipanggil, perubahan status tidak relevan.
Apakah ini hanya efek samping dari kompilator menggunakan kembali kode pembangkitan iterator blok untuk async, di mana ia mungkin memiliki penjelasan yang lebih jelas?
Penafian penting
Jelas ini hanyalah kompiler CTP. Saya sepenuhnya berharap hal-hal berubah sebelum rilis final - dan bahkan mungkin sebelum rilis CTP berikutnya. Pertanyaan ini sama sekali tidak mencoba untuk mengklaim ini adalah cacat dalam kompiler C # atau sesuatu seperti itu. Saya hanya mencoba mencari tahu apakah ada alasan halus untuk ini yang saya lewatkan :)