【问题标题】:Is there a difference between lambdas declared with and without async使用和不使用异步声明的 lambda 之间有区别吗
【发布时间】:2016-05-10 08:34:49
【问题描述】:

当 lambda 表达式 () => DoSomethingAsync()async () => await DoSomethingAsync() 都输入为 Func<Task> 时,它们之间有区别吗?我们应该在什么时候选择哪一个?

这是一个简单的控制台应用程序

using System;
using System.Threading.Tasks;

namespace asyncDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var demo = new AsyncDemo();
            var task = demo.RunTheDemo();
            task.Wait();

            Console.ReadLine();
        }
    }

    public class AsyncDemo
    { 
        public async Task Runner(Func<Task> action)
        {
            Console.WriteLine(DateTime.Now.ToLongTimeString() + " Launching the action");
            await action();
        }

        private async Task DoSomethingAsync(string suffix)
        {
            await Task.Delay(2000);
            Console.WriteLine(DateTime.Now.ToLongTimeString() + " Done something, " + suffix);
        }

        public async Task RunTheDemo()
        {
            await Runner(() => DoSomethingAsync("no await"));
            await Runner(async () => await DoSomethingAsync("with await"));
        }
    }
}

输出是:

09:31:08 Launching the action
09:31:10 Done something, no await
09:31:10 Launching the action
09:31:12 Done something, with await

所以在RunTheDemo 中,对await Runner(someLambda); 的两个调用似乎都以相同的时间特性执行相同的操作 - 都具有正确的两秒延迟。

两条线都有效,那么它们是否完全相同? () =&gt; DoSomethingAsync()async () =&gt; await DoSomethingAsync() 构造之间有什么区别?我们应该在什么时候选择哪一个?

这与“我是否应该在一般情况下使用await”不是同一个问题,因为这里我们正在处理工作异步代码,lambdas 类型为Func&lt;Task&gt;,在消费方法中正确等待。问题在于这些 lambda 是如何声明的,以及声明的效果是什么。

【问题讨论】:

  • 它基本上是编译器简化了 lambda 并从 DoSomething 自动返回任务...添加括号,现在它倒下,您必须执行异步或手动返回任务。
  • 您的示例异步 lambda 是微不足道的 - 它相当于 return await 反模式(这是该方法中唯一使用的 await) - 您涉及所有异步机制没有目的。

标签: c# asynchronous lambda async-await


【解决方案1】:

使用和不使用异步声明的 lambda 有区别

是的,有区别。一个是异步 lambda,另一个只是返回任务的 lambda。

一个异步 lambda 被编译到状态机中,而另一个则没有,因此异步 lambda 具有不同的异常语义,因为异常被封装在返回的任务中并且不能同步抛出。

这与常规方法中存在的差异完全相同。例如在这个异步方法之间:

async Task FooAsync()
{
    await DoSomethingAsync("with await");
}

还有这个任务返回方法:

Task FooAsync()
{
    return DoSomethingAsync("no await");
}

查看这些方法可以更清楚地显示差异,但因为 lambdas 只是语法糖,实际上被编译成与这些方法行为相同的方法。

我们应该在什么时候选择哪一个?

这真的取决于你的口味。使用 async 关键字会生成一个状态机,其性能不如简单地返回任务。但是,在某些情况下,异常语义可能会令人惊讶。

以这段代码为例:

Hamster hamster = null;
Func<Task> asyncAction = () => FooAsync(hamster.Name);

var task = asyncAction();
try
{
    await task;
}
catch
{
    // handle
}

try-catch 块是否会处理NullReferenceException

不会因为调用asyncAction时同步抛出异常。然而,异常在这种情况下被处理,因为它在返回的任务中被捕获并在等待该任务时重新抛出。

Func<Task> asyncAction = async () => await FooAsync(hamster.Name);

我个人对这些单行表达式 lambda 使用返回任务的 lambda,因为它们通常非常简单。但是我的团队在经历了一些非常有害的错误之后,总是使用asyncawait 关键字。

【讨论】:

  • @Luaan This TryRoslyn example 在使用 async-await 时肯定会有所不同:
  • 好的,我认为 tl;dr 是这样的: - 它们在大多数方面是相同的。 - 但是异常处理不同,如果你需要正确的异常冒泡行为,你会更喜欢带有 async...await 的 lambda。 - 但是这个版本可能效率较低,因为它会生成一个额外的状态机。
【解决方案2】:

这是这两种方法的 IL Viewer 的输出:

await Runner(() => DoSomethingAsync("no await"));

    .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task 
'<RunTheDemo>b__5_0'() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
  = (01 00 00 00 )
.maxstack 8

// [42 32 - 42 60]
IL_0000: ldarg.0      // this
IL_0001: ldstr        "no await"
IL_0006: call         instance class [mscorlib]System.Threading.Tasks.Task TestClass::DoSomethingAsync(string)
IL_000b: ret
} // end of method CompanyManagementController::'<RunTheDemo>b__5_0'



await Runner(async () => await DoSomethingAsync("with await"));

.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task 
'<RunTheDemo>b__5_1'() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) 
  = (
    01 00 45 57 65 62 43 61 72 64 2e 43 6f 6e 74 72 // ..TestClass
    6f 6c 6c 65 72 73 2e 43 6f 6d 70 61 6e 79 4d 61 // +<<RunTheDemo>
    6e 61 67 65 6d 65 6e 74 43 6f 6e 74 72 6f 6c 6c // b__5_1>d..
    65 72 2b 3c 3c 52 75 6e 54 68 65 44 65 6d 6f 3e  
    62 5f 5f 35 5f 31 3e 64 00 00                    
  )
  // MetadataClassType(TestClass+<<RunTheDemo>b__5_1>d)
.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() 
  = (01 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
  = (01 00 00 00 )
.maxstack 2
.locals init (
  [0] class TestClass/'<<RunTheDemo>b__5_1>d' V_0,
  [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
)

IL_0000: newobj       instance void TestClass/'<<RunTheDemo>b__5_1>d'::.ctor()
IL_0005: stloc.0      // V_0
IL_0006: ldloc.0      // V_0
IL_0007: ldarg.0      // this
IL_0008: stfld        class TestClass TestClass/'<<RunTheDemo>b__5_1>d'::'<>4__this'
IL_000d: ldloc.0      // V_0
IL_000e: call         valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
IL_0013: stfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0018: ldloc.0      // V_0
IL_0019: ldc.i4.m1    
IL_001a: stfld        int32 TestClass/'<<RunTheDemo>b__5_1>d'::'<>1__state'
IL_001f: ldloc.0      // V_0
IL_0020: ldfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0025: stloc.1      // V_1
IL_0026: ldloca.s     V_1
IL_0028: ldloca.s     V_0
IL_002a: call         instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class TestClass/'<<RunTheDemo>b__5_1>d'>(!!0/*class TestClass/'<<RunTheDemo>b__5_1>d'*/&)
IL_002f: ldloc.0      // V_0
IL_0030: ldflda       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0035: call         instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
IL_003a: ret
} // end of method CompanyManagementController::'<RunTheDemo>b__5_1'

所以第二个是使用异步状态机

【讨论】:

  • 我认为您需要反编译 '&lt;RunTheDemo&gt;b__5_0''&lt;RunTheDemo&gt;b__5_1' 以查看实际差异。
【解决方案3】:

是的,它们是相同的,但这是一个相当简单的示例。两者在功能上是等效的,您只是(可能取决于编译器)在使用async 时做更多的工作。

了解async lambda 为何有用的一个更好的例子是,如果您需要处理一系列异步操作 - 毕竟这就是 await 的用途:

await Runner(async () => await DoSomethingAsync(await httpClient.Get("www.google.com")));

【讨论】:

  • 没错,使用 await 最重要的事情是有目的的使用它。 OP 愚蠢的例子就像使用异步的同步调用......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-15
  • 2013-06-05
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 2016-04-20
  • 1970-01-01
相关资源
最近更新 更多