ET9中ETTask传递新的Context原理
前言
每一个异步函数都会创建两个对象,
第1个是当前异步函数返回值(ETTASK)对应的ETAsyncTaskMethodBuilder,通过这个类的静态方法Create创建返回,这个builder类中会有一个Task对象,用于当返回值返回;
第2个是编译器为我们创建的对应的状态机对象。其中状态机对象包裹了当前异步函数的所有代码。
对应的源码
以及包裹的状态机代码:
一、ETTask传递上下文的关键代码
如上图中红框所示,只要在源码中调用了await,就会执行对应的ETAsyncTaskMethodBuilder的AwaitUnsafeOnCompleted函数,这里就是传递上下文的入口。在一个异步函数(父异步)中,每次await调用另外一个异步函数(称为子异步吧),都会获得这个子异步函数对应的builder中的Task,又是通过父异步函数的builder对象的AwaitUnsafeOnCompleted来实现的。所以此时可以拿到此时父异步函数builder中的Task的上下文对象,传递给AwaitUnsafeOnCompleted中的子异步函数builder返回的builder中的Task。
ET9中的源码:
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
this.iStateMachineWrap ??= StateMachineWrap<TStateMachine>.Fetch(ref stateMachine);
awaiter.UnsafeOnCompleted(this.iStateMachineWrap.MoveNext);
if (awaiter is not IETTask task)
{
return;
}
if (this.tcs.TaskType == TaskType.WithContext)
{
task.SetContext(this.tcs.Context);
return;
}
this.tcs.Context = task;
}
可以看到获取当前tcs(也就是builder中的Task)的类型是附带有上下文的,那么就进行一次设置子Task的上下文设置,如果不是携带上下文,就把当前Task上下文设置为这个子Task,用于后续上下文传递。
SetContext这是个Task的扩展函数,内部是循环,设置当前Task的上下文同时,找到对应的子Task,如果子Task不存在,或者已经是附带上下文了,就返回。
while (true)
{
if (task.TaskType == TaskType.ContextTask)
{
((ETTask<object>)task).SetResult(context);
break;
}
// cancellationToken传下去
task.TaskType = TaskType.WithContext;
object child = task.Context;
task.Context = context;
task = child as IETTask;
if (task == null)
{
break;
}
传递到WithContext为止,因为可能这一层设置了新的context
if (task.TaskType == TaskType.WithContext)
{
break;
}
}
通过上面的代码,就可以理解为,当前异步函数中,只要await了一个ETTask,那么就会把当前异步函数ETTask(由对应的builder创建的)的上下文,传递给这个ETTask(没有设置自己的上下文),无论这个ETTask是由新的异步函数创建返回的,还是我们手动ETTask.Create的。
二、GetContextAsync怎么获取到当前上下文的
有了上面的介绍,我们就能知道,ETTask的上下文如何进行传递的。那么在一个异步函数中,要获取当前异步函数的上下文,那么就可以await ETTaskHelper.GetContextAsync来获取。
源码如下:
public static async ETTask<T> GetContextAsync<T>() where T: class
{
ETTask<object> tcs = ETTask<object>.Create(true);
tcs.TaskType = TaskType.ContextTask;
object ret = await tcs;
if (ret == null)
{
return null;
}
return (T)ret;
}
当一个异步函数await这个GetContextAsync,那么进入这个函数时,外层的异步函数的ETTask上下文就传递到这个异步函数中(强调一下,由这个异步函数对应的Builder的创建的ETTask中)。然后内部创建了一个ETTask,再一次Await,那么上下文就由builder的Task传递到内部这个创建的ETTask。
要注意的是,内部创建的ETTask类型是ContextTask,那么在await–>AwaitUnsafeOnCompleted->SetContext,最终调用到SetContext时,有如下代码:
if (task.TaskType == TaskType.ContextTask)
{
((ETTask<object>)task).SetResult(context);
break;
}
即立刻调用此ETTask的SetResult的结果为上下文,然后ETTask会调用callback(由AwaitUnsafeOnCompleted传给ETTask的,状态机的MoveNext委托),就会返回到GetContextAsync函数中,获取结果并返回,这个结果就是当前异步函数的上下文信息了。
总结
ET9作者猫大的创新以及设计能力确实强,新版的ETTask也确实牛逼,有了上下文确实能搞出很多花样了。