任何区别“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.Result
或task.GetAwaiter().GetResult()
观察await task
。 即使从async
方法的同步部分抛出,也会以这种方式传播。
考虑下面的代码,其中OneTestAsync
和AnotherTestAsync
行为完全不同:
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; }
?
无论我的两件事情有什么不同,两件事情的差别也是一样的。
-
第一种方法甚至没有编译。
由于“
Program.TestAsync()
”是一个返回“Task
”的asynchronous方法,所以return关键字后面不能跟一个对象expression式。 你打算返回“Task<T>
”吗?它一定要是
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
-
这两者在概念上有很大的区别。 第一个是asynchronous的,第二个不是。 阅读asynchronous性能:了解asynchronous和等待的成本,以获得更多有关
async
/await
内部的信息。 -
他们确实生成不同的代码。
.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,应该做的是提高方法的效率。
看到这个问题和这个答案是非常类似于你的问题和这个答案。