确实可以实现异步表达式树,但是没有框架支持(还没有?)来构建异步表达式树。因此,这绝对不是一项简单的工作,但我在日常生产使用中有几个实现。
所需材料如下:
-
派生自TaskCompletionSource 的帮助器类,用于提供任务以及与之相关的所有必需内容。
我们需要添加一个 State 属性(您可以使用不同的名称,但这与 C# 编译器为 async-await 生成的帮助程序保持一致)它跟踪状态机当前处于哪个状态。
然后我们需要有一个MoveNext 属性,即Action。这将被调用以处理状态机的下一个状态。
最后我们需要一个地方来存储当前待处理的Awaiter,这将是一个对象类型的属性。
异步方法通过使用SetResult、SetException(或SetCanceled)终止。
这样的实现可能如下所示:
internal class AsyncExpressionContext<T>: TaskCompletionSource<T> {
public int State {
get;
set;
}
public object Awaiter {
get;
set;
}
public Action MoveNext {
get;
}
public AsyncExpressionContext(Action<AsyncExpressionContext<T>> stateMachineFunc): base(TaskCreationOptions.RunContinuationsAsynchronously) {
MoveNext = delegate {
try {
stateMachineFunc(this);
}
catch (Exception ex) {
State = -1;
Awaiter = null;
SetException(ex);
}
};
}
}
- 一个状态机 lambda 表达式,将实际状态机实现为 switch 语句,类似这样(不按原样编译,但应该说明需要做什么):
var paraContext = Expression.Parameter(AsyncExpressionContext<T>, "context");
var stateMachineLambda = Expression.Lambda<Action<AsyncExpressionContext<T>>>(Expression.Block(new[] { varGlobal },
Expression.Switch(typeof(void),
Expression.Property(paraContext, nameof(AsyncExpressionContext<T>.State)),
Expression.Throw(
Expression.New(ctor_InvalidOperationException, Expression.Constant("Invalid state"))),
null,
stateMachineCases));
每个案例都实现了状态机的一种状态。总的来说,我不会过多地讨论 async-await 状态机概念的细节,因为有很好的资源可用,尤其是许多博客文章,它们对所有内容都进行了详细的解释。
https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/
通过利用标签和 goto 表达式(如果它们不带值,它们可以跳过块),当异步方法在被调用后同步返回时,可以实现“热路径优化”。
基本概念是这样的(伪代码):
State 0 (start state):
- Initiate async call, which returns an awaitable object.
- Optionally and if present call ConfigureAwait(false) to get another awaiter.
- Check the IsCompleted property of the awaiter.
- If true, call GetResult() on the awaiter and store the the result in a "global" variable, then jump to the label "state0continuation"
- If false, store the awaiter and the next state in the context object, then call OnCompleted(context.MoveNext) on the awaiter and return
State X (continuation states):
- Cast the awaiter from the context object back to its original type and call GetResult(), store its result in the same "global" variable.
- Label "state0continuation" goes here; if the call was synchronous we already have our value in the "global" variable
- Do some non-async work
- To end the async call, call SetResult() on the context and return (setting the state property to an invalid value and clearing the awaiter property may be a good idea for keeping things tidy)
- You can make other async calls just as shown in state 0 and move to other states
- 创建 TaskCompletionSource 并启动状态机的“引导程序”表达式。这将作为异步 lambda 公开。当然,您也可以添加参数并通过闭包传递它们或将它们添加到上下文对象中。
var varContext = Expression.Variable(typeof(AsyncExpressionContext<T>), "context");
var asyncLambda = Expression.Lambda<Func<Task<T>>>(
Expression.Block(
Expression.Assign(
varContext,
Expression.New(ctor_AsyncExpressionContext,
Expression.Lambda<Action<AsyncExpressionContext<T>>>(
stateMachineExression,
paraContext))),
Expression.Invoke(
Expression.Property(varContext, nameof(AsyncExpressionContext<T>.MoveNext)),
varContext),
Expression.Property(varContext, nameof(AsyncExpressionContext<T>.Task)));
这几乎是线性异步方法所需的全部内容。如果你想添加条件分支,为了让流程正确地进入下一个状态,事情会变得有点棘手,但它同样可能并且有效。