其实这篇文章的内容可以参考《CLR via C#》里的第28章,不过总觉得书上的还不够直观,所以干脆自己来整理一下好了。

新建一个控制台项目,代码如下,三个非常简单的方法。

static async Task<string> MyMethodAsync(int argument)
{
	var t1 = await GetType1();
	var t2 = await GetType2();
	return "Complete";
}

static async Task<Type> GetType1()
{
	await Task.Run(() => { Console.WriteLine("GetType1"); });
	return typeof(string);
}

static async Task<Type> GetType2()
{
	await Task.Run(() => { Console.WriteLine("GetType2"); });
	return typeof(int);
}

现在用ILSpy将其转换为C# 4.0的代码之后(因为C# 4.0没有async await,所以能看到状态机),我们来看看状态机的原理: <!-- more -->

代码经过可读性优化,并非原版。

/* 指出这是一个异步方法,并指出状态机的实现是哪个 class\struct */
[AsyncStateMachine(typeof(<MyMethodAsync>d__1))]
[DebuggerStepThrough]
private static Task<string> MyMethodAsync(int argument)
{
	// 创建状态机实例并初始化
	<MyMethodAsync>d__1 stateMachine = new <MyMethodAsync>d__1();
	stateMachine.argument = argument;   // 将方法实参拷贝到状态机的字段上
	// 创建 builder ,从这个方法存根(我觉得理解为变量即可)上返回 Task<string>
	stateMachine.t__builder = AsyncTaskMethodBuilder<string>.Create();
	stateMachine.m_state = -1;   // 设置状态的初始位置

	// 开始执行状态机
	stateMachine.t__builder.Start(ref stateMachine);
	return stateMachine.t__builder.Task;   // 返回状态机的 Task
}

// 这两个方法不重要,当摆设展示下结构即可
[AsyncStateMachine(typeof(<GetType1>d__2))]
[DebuggerStepThrough]
private static Task<Type> GetType1()

[AsyncStateMachine(typeof(<GetType2>d__3))]
[DebuggerStepThrough]
private static Task<Type> GetType2()

/* 
这是状态机本身,Release 下是 struct
原因:https://stackoverflow.com/questions/23609110/why-are-awaiters-async-await-structs-and-not-classes-can-classes-be-used
*/
[CompilerGenerated]
private sealed class <MyMethodAsync>d__1 : IAsyncStateMachine
{
	/* 当前状态机的位置 */
	public int m_state;
	/* 状态机的 builder */
	public AsyncTaskMethodBuilder<string> t__builder;
	
	/* 
	MyMethodAsync 方法的实参和局部变量此时会变成状态机的字段
	所以一共有三个
	*/
	public int argument;
	private Type m_t1;
	private Type m_t2;

	/* 
	每个 awaiter 类型一个字段,我这里 GetType1 跟 GetType2 都返回的是 Type 类型
	所以这里只有一个字段
	当你的方法中涉及到多个 Awaiter 类型时,就会有多个字段,但任何时候只有最近执行的、完成的那一个字段是重要的
	*/
private TaskAwaiter<Type> m_awaiterType1;

	/*
	这是状态机方法
	*/
	void IAsyncStateMachine.MoveNext()
	{
		int num = m_state;
		string result;   // 结果值
		try   // 编译器插入 try 块保证状态机的任务完成
		{
			TaskAwaiter<Type> awaiter1;
			TaskAwaiter<Type> awaiter2;

			switch (num)
			{
			/* 
			这里理解成case -1就行了,意为状态机第一次执行
			也就是 var t1 = await GetType1();
			*/
			default:
				awaiter1 = GetType1().GetAwaiter();   // 调用 GetType1 方法并拿到其 Awaiter
				if (!awaiter1.IsCompleted)   // 这里做判断是避免多次执行
				{
					num = (m_state = 0);   // 等一会儿返回状态机时走到哪个位置去
					m_awaiterType1 = awaiter1;  // 保存 awaiter 以便将来返回
					stateMachine = this;
					/* 
					告诉 awaiter 在 GetType1 的异步方法执行完毕后,调用 MoveNext
					具体一点,这句代码会调用 awaiterType1 的 OnCompleted
					它会在被等待的任务上调用 ContinueWith(t=> MoveNext()...
					表示任务完成后调用 MoveNext 方法重返状态机
					*/
					t__builder.AwaitUnsafeOnCompleted(ref awaiter1, ref stateMachine);
					
					/* 
						线程返回至调用者
						*/
					return;
				}
				goto case unNamed;
			/* GetType1 方法异步完成了 */
			case 0:
				awaiter1 = m_awaiterType1;   // 恢复最新的 awaiter
				
				m_t1 = awaiter1.GetResult();  // 获取 GetType1 方法的结果
				// 开始调用 GetType2
				awaiter2 = GetType2().GetAwaiter();
				if (!awaiter2.IsCompleted)
				{
					num = (m_state = 1);   // 下一次 MoveNext 时走到 case 1 中
					m_awaiterType1 = awaiter2;
					t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
					return;
				}
				break;
			case 1:
				{
					awaiter2 = m_awaiterType1;   // 恢复最新的 awaiter
					
					m_t2 = awaiter2.GetResult();   // 获取 GetType2 方法的结果
					result = "Complete";   // 设置 MyMethodAsync 方法最终的返回结果
					// 注意这里没有 break,还要继续执行
				}
		}
		catch (Exception exception)
		{
			// 有异常:通过设置异常来完成状态机的 Task
			t__builder.SetException(exception);
			return;
		}
		// 无异常:通过返回结果来完成状态机的 Task
		t__builder.SetResult(result);
	}
}

状态机总结

简单总结以下状态机的几个工作要点:

  1. 安排好awaiter之后就返回原调用线程避免阻塞
  2. 在安排时更新状态机恢复之后需要执行的位置(m_state
  3. 安排后在awaiter的任务上调用ContinueWith(t=> MoveNext())来以便返回状态机

可以简单理解为你的async方法中有几个await,以上三步就要在状态机中重复几次。