【问题标题】:Async lambda to Expression<Func<Task>>异步 lambda 到 Expression<Func<Task>>
【发布时间】:2015-07-21 15:31:41
【问题描述】:

众所周知,我可以将普通的lambda表达式转换为Expression&lt;T&gt;

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

有没有可能的解决方法?

【问题讨论】:

  • 你不会碰巧在使用 Mono.Net 吧?
  • 我从带有新发布的 VS2015 的 C# 6 编译器中得到了这个错误。我认为这种情况在 C# 5 和/或 Mono 编译器上不会改变。如果可以 - 请告诉我。

标签: c# lambda async-await


【解决方案1】:

只有当代码可以用表达式树表示时,C#才能将lambda表达式转换为表达式树,如果你注意到System.Linq.Expressions中的表达式中没有“async”关键字的等效项

因此,不仅是异步的,而且 C# 中提供的表达式中没有等效表达式的任何内容,C# 都无法将其转换为表达式树。

其他例子是

  1. 不安全
  2. 使用
  3. 产量
  4. 等待

【讨论】:

  • awaityield break / return 之外的任何其他此类语言结构示例?
【解决方案2】:

这个错误很容易解释:

“无法将异步 lambda 表达式转换为表达式树”

它也记录在Async/Await FAQ

并且有充分的理由,async-await 是框架之上的编译器功能。表达式用于将代码转换为其他命令(如 SQL)。这些其他语言可能没有 async-await 等效项,因此通过表达式启用它似乎不值得。

所以不,我认为没有解决方法。

【讨论】:

  • 嗯,这是一个有趣的链接。不知道我怎么会错过那个常见问题解答。
【解决方案3】:

(迟到的答案)

你可以重写这个:

Expression&lt;Func&lt;Task&lt;int&gt;&gt;&gt; bar2 = async () =&gt; 0;

到这里:

Expression&lt;Func&lt;Task&lt;int&gt;&gt;&gt; bar2 = () =&gt; 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 的方法/委托(这是常规方法,可以等待)。然后编译器可以创建需要的东西来等待委托调用。

【讨论】:

    【解决方案4】:

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

    所需材料如下:

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

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

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

      最后我们需要一个地方来存储当前待处理的Awaiter,这将是一个对象类型的属性。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多