昨天我做了一个关于新的C#“异步”特性的演讲,特别是深入研究了生成的代码是什么样子的,以及the GetAwaiter()
/BeginAwait()
/EndAwait()
调用。
我们详细查看了C#编译器生成的状态机,有两个方面我们无法理解:
Dispose()
方法和一个$__disposing
变量,它们似乎从未被使用过(并且该类没有实现IDisposable
)。state
变量在任何对EndAwait()
的调用之前被设置为0,而0通常表示“这是初始入口点”。我怀疑第一点可以通过在异步方法中做一些更有趣的事情来回答,尽管如果任何人有任何进一步的信息,我会很高兴听到它。然而,这个问题更多的是关于第二点。
下面是一段非常简单的示例代码:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
...这是为__abc0方法生成的代码,它实现了状态机。这是直接从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#编译器中的一个缺陷或类似的东西。我只是想弄清楚是否有一个微妙的原因,我错过了:)