任何区别“await Task.Run(); 返回;“和”返回Task.Run()“?

以下两段代码之间有什么概念上的区别:

async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 

 Task TestAsync() { return Task.Run(() => DoSomeWork()); } 

生成的代码是否有所不同?

编辑:为了避免与Task.Run混淆类似的情况:

 async Task TestAsync() { await Task.Delay(1000); } 

 Task TestAsync() { return Task.Delay(1000); } 

最新更新:除了接受的答案之外, LocalCallContext的处理方式也有所不同: CallContext.LogicalGetData即使在没有asynchronous的情况下也会被恢复。 为什么?

更新 ,除了下面解释的exception传播行为的差异之外,还有另一个有点微妙的区别: async / await版本更倾向于在非默认同步上下文上进行死锁。 例如,以下将在WinForms或WPF应用程序中死锁:

 static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here } 

将其更改为非asynchronous版本,并且不会死锁:

 Task TestAsync() { return Task.Delay(1000); } 

Stephen Cleary在他的博客中很好地解释了死锁的本质。


另一个主要的区别是exception传播。async Task方法中引发的exception将存储在返回的Task对象中,并保持hibernate状态,直到通过await task task.Wait()task.Resulttask.GetAwaiter().GetResult()观察await task 。 即使从async方法的同步部分抛出,也会以这种方式传播。

考虑下面的代码,其中OneTestAsyncAnotherTestAsync行为完全不同:

 static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func<int, Task> whatTest, int n) { Task task = null; try { // start the task task = whatTest(n); // do some other stuff, // while the task is pending Console.Write("Press enter to continue"); Console.ReadLine(); task.Wait(); } catch (Exception ex) { Console.Write("Error: " + ex.Message); } } 

如果我调用DoTestAsync(OneTestAsync, -2) ,它会产生以下输出:

按回车继续
错误:发生一个或多个错误。等待Task.Delay
错误:第二

请注意,我必须按Enter才能看到它。

现在,如果我调用DoTestAsync(AnotherTestAsync, -2) ,那么DoTestAsync的代码工作stream程就完全不同了,输出也是如此。 这一次,我没有被要求按Enter键

错误:该值需要为-1(表示无限超时),0或正整数。
参数名称:millisecondsDelayError:1st

在这两种情况下, Task.Delay(-2)在开始时抛出,同时validation其参数。 这可能是一个Task.Delay(1000)场景,但理论上Task.Delay(1000)也可能抛出,例如,当底层系统计时器API失败时。

在旁注中,错误传播逻辑对于async void方法 (与async Task方法相反)是不同的。 如果当前线程有一个( SynchronizationContext.Current != null) ,则async void方法中引发的exception将立即重新引发到当前线程的同步上下文(通过SynchronizationContext.Post SynchronizationContext.Current != null) 。 否则,将通过ThreadPool.QueueUserWorkItem重新引发。 调用者没有机会在同一个栈帧上处理这个exception。

我在这里和这里发布了更多关于TPLexception处理行为的细节。


:是否可以模仿非async方法的async传播行为,以便后者不会抛出相同的堆栈框架?

:如果真的需要,那么是的,这是一个技巧:

 // async async Task<int> MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task<int> MethodAsync(int arg) { var task = new Task<int>(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; } 

但是请注意, 在某些情况下 (如堆栈太深时), RunSynchronously仍然可以asynchronous执行。

有什么区别

 async Task TestAsync() { await Task.Delay(1000); } 

 Task TestAsync() { return Task.Delay(1000); } 

我很困惑这个问题。 让我试着用另一个问题回答你的问题来澄清。 有什么区别?

 Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); } 

 Func<int> MakeFunction() { return ()=>1; } 

无论我的两件事情有什么不同,两件事情的差别也是一样的。

  1. 第一种方法甚至没有编译。

    由于“ Program.TestAsync() ”是一个返回“ Task ”的asynchronous方法,所以return关键字后面不能跟一个对象expression式。 你打算返回“ Task<T> ”吗?

    它一定要是

     async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 
  2. 这两者在概念上有很大的区别。 第一个是asynchronous的,第二个不是。 阅读asynchronous性能:了解asynchronous和等待的成本,以获得更多有关async / await内部的信息。

  3. 他们确实生成不同的代码。

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74 2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73 79 6e 63 3e 64 5f 5f 31 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x216c // Code size 62 (0x3e) .maxstack 2 .locals init ( [0] valuetype SOTestProject.Program/'<TestAsync>d__1', [1] class [mscorlib]System.Threading.Tasks.Task, [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ) IL_0000: ldloca.s 0 IL_0002: ldarg.0 IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this' IL_0008: ldloca.s 0 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0023: stloc.2 IL_0024: ldloca.s 2 IL_0026: ldloca.s 0 IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_0039: stloc.1 IL_003a: br.s IL_003c IL_003c: ldloc.1 IL_003d: ret } // end of method Program::TestAsync 

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed { // Method begins at RVA 0x21d8 // Code size 23 (0x17) .maxstack 2 .locals init ( [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2 

这两个例子有所不同。 当一个方法用async关键字标记时,编译器会在幕后生成一个状态机。 这是什么是负责恢复延续一旦等待已经等待。

相反,当一个方法没有被标记为async你正在失去await对等的能力。 (也就是说,在方法本身中,方法仍然可以由调用者等待)。但是,通过避免使用async关键字,您不再生成状态机,这可能会增加公平的开销(提升本地状态机的字段,GC的附加对象)。

在这样的例子中,如果你能够避免async-await并直接返回awaitable,应该做的是提高方法的效率。

看到这个问题和这个答案是非常类似于你的问题和这个答案。