ET9中ETTask传递新的Context原理

发布于:2024-06-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

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也确实牛逼,有了上下文确实能搞出很多花样了。