有各种反编译异步代码的示例展示了异步状态机的内部。但它看起来很复杂,目前还不清楚它是什么,以及它是如何工作的。
是否可以以一个简单的异步方法为例,在行为上编写完全相同,但没有async/await? 要使用调试器,请逐步了解该方法以了解其工作原理。
比如这个:
private static async Task GoAsync()
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(200);
Console.WriteLine(i);
}
for (int i = 10; i > 0; i--)
{
await Task.Delay(200);
Console.WriteLine(i);
}
}
该方法只计算从 1 到 10 和从 10 到 1 的延迟。
发射:
static async Task Main(string[] args)
{
await GoAsync();
Console.WriteLine("Done.");
Console.ReadKey();
}
我将给出一个更现实的实现。首先,让我们用循环替换所有循环
while并对状态进行编号:对状态进行编号的条件很简单——您需要对起始点 (0)、可以通过多种方式执行的任何点(1 和 3)以及等待操作符的返回点(2 和 4)进行编号。
现在我们可以开始编写状态机了:
生成的状态机大致对应于编译器构建的状态机,但有以下区别:
completion不会立即创建,而是延迟创建,以免在函数返回时未执行 await 运算符而创建不必要的对象;实现一个自制的状态机有一点困难:它不能完全适应一种方法。除了状态之外,该方法还有变量,在这种情况下
i,它必须存储在某个地方。因此,将使用几个附加字段。为了不一头扎进手动线程,我将使用线程池
ThreadPool,为了管理任务,我将使用TaskCompletionSource. 另外,为了便于实现,我将使用Thread.Sleep它来代替它的异步对应物,否则我将不得不编写另一个状态机并将其与主状态机相关联。这个答案(以及问题)的目的是展示最简单的DIY状态机。这是解决方案,在注释中我标记了哪些代码行执行与原始方法相同的功能:
这是有限状态机设计模式的实现,或者也称为状态机。
现在您可以设置断点并在调试器中直观地查看它是如何工作的。让我提醒你,这个实现被大大简化了,并且有它的局限性,例如,如果该方法并行运行两次,一切都会中断。