昨天我做了一场关于新的C#“async”功能的讲座,特别是深入研究生成的代码以及GetAwaiter()
/ BeginAwait()
/ EndAwait()
调用。
我们详细研究了由C# 编译器生成的状态机,并有两个方面我们无法理解:
- 为什么生成的类包含一个
Dispose()
方法和一个$__disposing
变量,它们似乎从未被使用(并且该类不实现IDisposable
)。 - 为什么在任何对
EndAwait()
的调用之前,内部的state
变量都被设置为0,而通常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
),它实际上会重新启动异步方法,据我所知这完全不合适...如果MoveNext()
没有被调用,则状态的更改是无关紧要的。这是否只是编译器在重用迭代器块生成代码以用于异步时的副作用,其中可能有一个更明显的解释?
重要免责声明
显然这只是 CTP 编译器。我完全希望在最终发布之前 - 可能甚至在下一个 CTP 发布之前 - 事情会发生变化。本问题并不试图声称这是 C# 编译器的缺陷或类似的任何内容。我只是想弄清楚是否有我错过的微妙原因 :)