[英]Async lambda to Expression<Func<Task>>
众所周知,我可以将普通的 lambda 表达式转换为Expression<T>
:
Func<int> foo1 = () => 0; // delegate compiles fine
Expression<Func<int>> foo2 = () => 0; // expression compiles fine
我怎么能用异步 lambda 做同样的事情? 我试过以下类比:
Func<Task<int>> bar1 = async () => 0; // also compiles (async lambda example)
Expression<Func<Task<int>>> bar2 = async () => 0; // CS1989: Async lambda expressions cannot be converted to expression trees
有没有可能的解决方法?
只有当代码可以由表达式树表示时,C# 才能将 lambda 表达式转换为表达式树,如果您注意到System.Linq.Expressions中的表达式中没有等效的“async”关键字
因此,不仅是异步的,而且 C# 中提供的表达式中没有等效表达式的任何内容,C# 都无法将其转换为表达式树。
其他例子是
该错误是不言自明的:
“异步 lambda 表达式无法转换为表达式树”
它也记录在Async/Await FAQ 中。
并且有充分的理由, async-await
是框架之上的编译器功能。 表达式用于将代码转换为其他命令(如 SQL)。 这些其他语言可能没有async-await
等效项,因此通过表达式启用它似乎不值得。
所以不,我看不到解决方法。
(迟到的回答)
你可以重写这个:
Expression<Func<Task<int>>> bar2 = async () => 0;
对此:
Expression<Func<Task<int>>> bar2 = () => Task.FromResult(0);
现在您可以创建返回Task
委托:
var result = bar2.Compile();
并等待它:
await result.Invoke();
我知道这是一个简单的例子,但是可以在没有await
情况下编写代码,但是使用Task.ContinueWith
或类似的东西:
Expression<Func<Task<int>>> moreComplex = () =>
SomeAsyncOperation() // can't be awaited as lambda is not marked as async
.ContinueWith(completedTask => /* continuationLogic */)
.Unwrap(); // to get unwrapped task instead of Task<Task>
您不能将async/await
与Expression
(因为它是编译器的东西),但您可以使用返回Task
方法/委托(这是常规方法,可以等待)。 然后编译器可以创建等待委托调用所需的东西。
确实可以实现异步表达式树,但是没有框架支持(还没有?)来构建异步表达式树。 因此,这绝对不是一项简单的任务,但我在日常生产使用中有几个实现。
所需材料如下:
从TaskCompletionSource派生的辅助类,用于提供任务以及与之相关的所有必需内容。
我们需要添加一个State
属性(您可以使用不同的名称,但这与 C# 编译器为 async-await 生成的帮助程序保持一致)来跟踪状态机当前所处的状态。
然后我们需要有一个MoveNext
属性,它是一个Action
。 这将被调用以处理状态机的下一个状态。
最后,我们需要一个地方来存储当前挂起的Awaiter
,这将是一个 object 类型的属性。
异步方法通过使用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);
}
};
}
}
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
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)));
这几乎是线性异步方法所需的全部内容。 如果你想添加条件分支,为了让流到下一个状态正确,事情会变得有点棘手,但它也是可能的并且可以工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.