昨天,我在谈论新的C#“异步”功能,特别是深入研究了生成的代码和the GetAwaiter()
/ BeginAwait()
/ EndAwait()
调用。
我们详细研究了C#编译器生成的状态机,其中有两个我们无法理解的方面:
- 为什么生成的类包含一个
Dispose()
方法和一个$__disposing
变量,而它们似乎从未被使用过(而该类未实现IDisposable
)。 - 为什么在
state
调用之前将内部变量设置为0EndAwait()
,通常0表示“这是初始入口点”。
我怀疑可以通过在async方法中做一些更有趣的事情来回答第一点,尽管如果有人有更多的信息,我很乐意听到。但是,这个问题更多地是关于第二点的。
这是一个非常简单的示例代码:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
...这是为MoveNext()
实现状态机的方法生成的代码。这是直接从Reflector复制的-我还没有解决难以形容的变量名:
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);
}
}
很长,但是此问题的重要内容如下:
// 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();
在这两种情况下,状态在随后明显被观察到之前都再次改变了。那么为什么要将其设置为0?如果MoveNext()
在这一点(直接或通过Dispose
)再次被调用,它将有效地再次启动async方法,据我所知,如果MoveNext()
不被调用,则状态改变是无关紧要的。
这仅仅是编译器为异步使用迭代器块生成代码的副作用,它在其中可能有更明显的解释?
重要免责声明
显然,这只是一个CTP编译器。我完全希望在最终版本发布之前,甚至在下一个CTP版本发布之前,一切都会有所改变。这个问题绝不是要声称这是C#编译器或类似工具中的缺陷。我只是想弄清楚我是否错过了一个微妙的原因:)