繁体   English   中英

将 lambda 异步到表达式<Func<Task> &gt;

[英]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# 都无法将其转换为表达式树。

其他例子是

  1. 不安全
  2. 使用
  3. 屈服
  4. 等待

该错误是不言自明的:

“异步 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/awaitExpression (因为它是编译器的东西),但您可以使用返回Task方法/委托(这是常规方法,可以等待)。 然后编译器可以创建等待委托调用所需的东西。

确实可以实现异步表达式树,但是没有框架支持(还没有?)来构建异步表达式树。 因此,这绝对不是一项简单的任务,但我在日常生产使用中有几个实现。

所需材料如下:

  • TaskCompletionSource派生的辅助类,用于提供任务以及与之相关的所有必需内容。

    我们需要添加一个State属性(您可以使用不同的名称,但这与 C# 编译器为 async-await 生成的帮助程序保持一致)来跟踪状态机当前所处的状态。

    然后我们需要有一个MoveNext属性,它是一个Action 这将被调用以处理状态机的下一个状态。

    最后,我们需要一个地方来存储当前挂起的Awaiter ,这将是一个 object 类型的属性。

    异步方法通过使用SetResultSetException (或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)));

这几乎是线性异步方法所需的全部内容。 如果你想添加条件分支,为了让流到下一个状态正确,事情会变得有点棘手,但它也是可能的并且可以工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM