编辑:我创建了一个库,实现将普通表达式树转换为异步表达式树。它不是使用专门的类型来存储状态,而是使用外部lambda的闭包,并使用每个awaiter类型一个变量(类似于C#状态机)来实现。
https://github.com/avonwyss/bsn.AsyncLambdaExpression
“确实可以实现异步表达式树,但目前没有框架支持构建异步表达式树(还没有吗?)。因此,这绝对不是一个简单的任务,但我已经有几个在日常生产中使用的实现。”
“所需的材料如下:”
从
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));
每个案例都实现了状态机的一个状态。我不会详细介绍异步等待状态机概念,因为有很多优秀的资源可用,特别是许多博客文章,它们详细解释了一切。
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)));
这基本上就是线性异步方法所需的全部内容。如果您想添加条件分支,为了使流程正确地进入下一个状态,需要做一些更棘手的事情,但同样也是可能的并且可行的。