Hôm qua tôi đã nói chuyện về tính năng "không đồng bộ" C # mới, đặc biệt đi sâu vào việc mã được tạo trông như thế nào và the GetAwaiter()
/ BeginAwait()
/ EndAwait()
cuộc gọi.
Chúng tôi đã xem xét một số chi tiết tại máy trạng thái được tạo bởi trình biên dịch C # và có hai khía cạnh chúng tôi không thể hiểu:
- Tại sao lớp được tạo chứa một
Dispose()
phương thức và một$__disposing
biến, dường như không bao giờ được sử dụng (và lớp không thực hiệnIDisposable
). - Tại sao
state
biến nội bộ được đặt thành 0 trước bất kỳ cuộc gọi nàoEndAwait()
, khi 0 thường xuất hiện có nghĩa là "đây là điểm nhập ban đầu".
Tôi nghi ngờ điểm đầu tiên có thể được trả lời bằng cách thực hiện một điều thú vị hơn trong phương thức async, mặc dù nếu có ai có thêm thông tin nào tôi sẽ rất vui khi nghe nó. Câu hỏi này là nhiều hơn về điểm thứ hai, tuy nhiên.
Đây là một đoạn mã mẫu rất đơn giản:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... Và đây là mã được tạo cho MoveNext()
phương thức thực hiện máy trạng thái. Điều này được sao chép trực tiếp từ Reflector - Tôi chưa sửa các tên biến không thể nói rõ:
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);
}
}
Nó dài, nhưng các dòng quan trọng cho câu hỏi này là:
// 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();
Trong cả hai trường hợp, trạng thái được thay đổi một lần nữa sau đó trước khi nó được quan sát rõ ràng ... vậy tại sao lại đặt nó thành 0? Nếu MoveNext()
được gọi lại vào thời điểm này (trực tiếp hoặc thông qua Dispose
), nó sẽ khởi động lại phương thức async một cách hiệu quả, điều này hoàn toàn không phù hợp theo như tôi có thể nói ... nếu và MoveNext()
không được gọi, thay đổi trạng thái là không liên quan.
Đây có phải chỉ đơn giản là một tác dụng phụ của trình biên dịch tái sử dụng mã tạo khối lặp cho async, nơi nó có thể có một lời giải thích rõ ràng hơn?
Từ chối trách nhiệm quan trọng
Rõ ràng đây chỉ là một trình biên dịch CTP. Tôi hoàn toàn mong đợi mọi thứ sẽ thay đổi trước khi phát hành cuối cùng - và thậm chí trước cả phiên bản CTP tiếp theo. Câu hỏi này không có cách nào để khẳng định đây là một lỗ hổng trong trình biên dịch C # hoặc bất cứ điều gì tương tự. Tôi chỉ đang cố gắng tìm hiểu xem có một lý do tinh tế nào cho việc này mà tôi đã bỏ lỡ không :)