Async Await 和生成的 StateMachine





5.00/5 (11投票s)
显示 StateMachine 的内部结构。
引言
像大家一样,我深入研究了 .NET 4.5,想看看有什么新东西...但我没有找到关于状态机实际工作方式的任何好的逐步解释,所以我使用了 ilspy,并结合了 一个不错的视频 来自创建者,我对它是什么有了一个很好的概念。(也欢迎您来了解一下)。
状态机
我从某处读到你可以这样想
- 调用 "before" 代码
- 将 await 代码作为 task 调用
- 将 "after" 代码存储在 continuation task 中
但事实并非如此。
真正发生的事情实际上是这样的
- 在编译时
- 生成一个名为
StateMachine
的struct
- 包含用于保存函数局部状态的字段
- 创建一个
moveNext
函数,其中包含整个代码 - 代码被 await 调用分割成几个 case (机器状态)
- 一个创建并初始化该机器的调用代码替换了我们的 async 函数代码。
- 在运行时:
- 创建一个任务来运行机器代码
- 局部变量被 "提升" 到状态机中作为字段。
- 代码运行直到 await
- 运行 awaited 函数的 task
- 机器状态设置为下一个状态,以便下一个代码片段在唤醒时运行
- 安排一个唤醒事件
MoveNext
函数返回 (线程被释放以执行其他操作(更新 UI))- 当操作系统发出唤醒调用时:
- 调用处理 await continuation 的线程
CurrentSyncContext
用于选择正确的线程来运行它。- 可以使用以下方法更改此行为: await
task.ConfigureAwait(false);
- 下一个代码段运行,因为下一个状态在屈服控制之前已设置
- 计划另一个 await,[等等]
在代码中看到它
解码了两个 async 函数
// AsyncAwait.Engine.Downloader
public static async Task<string> DownloadHtmlAsyncTask(string url)
{
HttpClient httpClient = new HttpClient();
Debug.WriteLine("before await");
string result = await httpClient.GetStringAsync(url);
Debug.WriteLine("after await");
return result;
}
// AsyncAwait.ViewModel.MainWndViewModel
private async Task<string> DownloadWithUrlTrackingTaskAsync(string url)
{
Debug.WriteLine("before await1");
string Data = await DownloadHtmlAsyncTask(url);
Debug.WriteLine("before await2");
string Data1 = await DownloadHtmlAsyncTask(url);
Debug.WriteLine("the end.");
return Data;
}
反编译是使用 ilspy 完成的,其中“反编译 async 方法”已关闭。
第一个函数
DownloadHtmlAsyncTask
仅使用一个 await 调用。
这是初始化状态机的调用代码
[DebuggerStepThrough, AsyncStateMachine(typeof(AsyncMethods.<DownloadHtmlAsyncTask>d__0))]
public static Task<string> DownloadHtmlAsyncTask(string url)
{
AsyncMethods.<DownloadHtmlAsyncTask>d__0 <DownloadHtmlAsyncTask>d__;
<DownloadHtmlAsyncTask>d__.url = url;
<DownloadHtmlAsyncTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
//set initial machine state to -1
<DownloadHtmlAsyncTask>d__.<>1__state = -1;
AsyncTaskMethodBuilder<string> <>t__builder = <DownloadHtmlAsyncTask>d__.<>t__builder;
<>t__builder.Start<AsyncMethods.<DownloadHtmlAsyncTask>d__0>(ref <DownloadHtmlAsyncTask>d__);
return <DownloadHtmlAsyncTask>d__.<>t__builder.get_Task();
}
状态机定义:(我的评论内联)
using ConsoleApplication1;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <DownloadHtmlAsyncTask>d__0 : IAsyncStateMachine
{
//initial state is set to -1 by the machine's creation code
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
public string url;
public HttpClient <httpClient>5__1;
public string <result>5__2;
private TaskAwaiter<string> <>u__$awaiter3;
private object <>t__stack;
void IAsyncStateMachine.MoveNext()
{
string result;
try
{
int num = this.<>1__state;
//if (!Stop)
if (num != -3)
{
TaskAwaiter<string> taskAwaiter;
//machine starts with num=-1 so we enter
if (num != 0)
{
//first (+ initial) state code, run code before await is invoked
this.<httpClient>5__1 = new HttpClient();
Debug.WriteLine("before await");
//a task is invoked
taskAwaiter = this.<httpClient>5__1.GetStringAsync(this.url).GetAwaiter();
//[performance] check if this task has completed already,
//if it did, skip scheduling and boxing
if (!taskAwaiter.get_IsCompleted())
{
this.<>1__state = 0;
this.<>u__$awaiter3 = taskAwaiter;
//Schedules the state machine to proceed
//to the next action when the specified awaiter completes.
//Also: sending this state machine here will trigger it's boxing into heap.
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>,
AsyncMethods.<DownloadHtmlAsyncTask>d__0>(ref taskAwaiter, ref this);
//release cpu knowing our next step will be called by Framework.
return;
}
}
else
{
//set awaiter to null
taskAwaiter = this.<>u__$awaiter3;
this.<>u__$awaiter3 = default(TaskAwaiter<string>);
//set state to initial state (temporarily)
this.<>1__state = -1;
}
//second (+ final) state code (state=0): set result to member, printout
string arg_A5_0 = taskAwaiter.GetResult();
//set awaiter to null
taskAwaiter = default(TaskAwaiter<string>);
//set StateMachine's result field, and end code (print out)
string text = arg_A5_0;
this.<result>5__2 = text;
Debug.WriteLine("after await");
//set return task's result
result = this.<result>5__2;
}
}
//exception handling is done here
catch (Exception exception)
{
//set machine state to final state
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult(result);
}
[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
- 状态机创建为一个 struct,这意味着它在堆栈上
- 它被移动到
AwaitUnsafeOnCompleted
函数中的堆(更深入到机制中,它被强制转换为 IAsyncStateMachine,从而触发 boxing)。 - 如果我们等待的任务在检查
taskAwaiter.IsCoplete
之前结束,则可以跳过此调用。
此函数只有两个代码片段(两个状态),因为只有一个 await 分隔代码,这可能是一个标准场景,但不是一个有趣的场景。
当 StateMachine 有更多状态时会发生什么?这使我们进入了第二个示例...
第二个函数
DownloadHtmlAsyncTask - 使用两个 await 调用
调用代码
[DebuggerStepThrough, AsyncStateMachine(typeof(AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5))]
private Task<string> DownloadWithUrlTrackingTaskAsync(string url)
{
AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5 <DownloadWithUrlTrackingTaskAsync>d__;
<DownloadWithUrlTrackingTaskAsync>d__.<>4__this = this;
<DownloadWithUrlTrackingTaskAsync>d__.url = url;
<DownloadWithUrlTrackingTaskAsync>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
//set initial machine state to -1
<DownloadWithUrlTrackingTaskAsync>d__.<>1__state = -1;
AsyncTaskMethodBuilder<string> <>t__builder =
<DownloadWithUrlTrackingTaskAsync>d__.<>t__builder;
<>t__builder.Start<AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref <DownloadWithUrlTrackingTaskAsync>d__);
return <DownloadWithUrlTrackingTaskAsync>d__.<>t__builder.get_Task();
}
状态机定义:(阅读代码中的注释)
using ConsoleApplication1;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <DownloadWithUrlTrackingTaskAsync>d__5 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
public AsyncMethods <>4__this;
public string url;
public string <Data>5__6;
public string <Data1>5__7;
private TaskAwaiter<string> <>u__$awaiter8;
private object <>t__stack;
void IAsyncStateMachine.MoveNext()
{
string result;
try
{
TaskAwaiter<string> taskAwaiter;
//initialy state = -1, so we skip this at start
switch (this.<>1__state)
{
case -3:
//request machine to stop!
//machine will go to end state and set result if one exists.
goto IL_168;
case 0:
taskAwaiter = this.<>u__$awaiter8;
this.<>u__$awaiter8 = default(TaskAwaiter<string>);
//set state to initial state (temporarily)
this.<>1__state = -1;
goto IL_A1;
case 1:
taskAwaiter = this.<>u__$awaiter8;
this.<>u__$awaiter8 = default(TaskAwaiter<string>);
//set state to initial state (temporarily)
this.<>1__state = -1;
goto IL_121;
}
// first state code state=-1,
//printout, and await, then return control to scheduler
Debug.WriteLine("before await1");
taskAwaiter = AsyncMethods.DownloadHtmlAsyncTask(this.url).GetAwaiter();
if (!taskAwaiter.get_IsCompleted())
{
//set state to next step (0)
this.<>1__state = 0;
this.<>u__$awaiter8 = taskAwaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>,
AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref taskAwaiter, ref this);
return;
}
IL_A1:
//second state code (state = 0), set the result of the first call to member,
//printout, schedule next await, yield control
string arg_B0_0 = taskAwaiter.GetResult();
taskAwaiter = default(TaskAwaiter<string>);
string text = arg_B0_0;
this.<Data>5__6 = text;
Debug.WriteLine("before await2");
taskAwaiter = AsyncMethods.DownloadHtmlAsyncTask(this.url).GetAwaiter();
if (!taskAwaiter.get_IsCompleted())
{
//set state to next step (1)
this.<>1__state = 1;
this.<>u__$awaiter8 = taskAwaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>,
AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref taskAwaiter, ref this);
return;
}
IL_121:
//third state code (state = 1),
//set the result of the first call to member, printout,
//set the (function's) return task's result.
string arg_130_0 = taskAwaiter.GetResult();
taskAwaiter = default(TaskAwaiter<string>);
text = arg_130_0;
this.<Data1>5__7 = text;
Debug.WriteLine("the end.");
result = this.<Data>5__6;
}
catch (Exception exception)
{
//some exception handling: set end state (-2)
this.<>1__state = -2;
//set the exception in the builder
this.<>t__builder.SetException(exception);
return;
}
IL_168:
//if no exception set end state and result
this.<>1__state = -2;
this.<>t__builder.SetResult(result);
}
[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
关注点
- 开销
- 任何人都可以看到添加了“几个”额外的代码行。
- 但根据微软的说法
- 大约 40 个操作用于创建 async 状态机(微秒)。
- 此开销与其他较旧的 async 机制相似或更少。
- 只有在紧密循环中调用多个 awaits 时,这才会成为问题。
- 除此之外,在今天的 CPU 上,当它在非 GUI 线程上运行时,谁会注意到开销?或者更好 - 在 HDD 控制器上...
- 异常处理
-
Task.WhenAll
将抛出它聚合的任何任务中的任何异常。 - 这意味着第一个异常将被抛出。
task.Exception
属性将包含AggregateException
中的所有异常(您可以使用Task.Wait()
而不是 await)