以下两段代码之间在概念上有什么区别:
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
和
Task TestAsync() { return Task.Run(() => DoSomeWork()); }
生成的代码是否也不同?
编辑: 为避免与混淆Task.Run,类似的情况:
Task.Run
async Task TestAsync() { await Task.Delay(1000); }
Task TestAsync() { return Task.Delay(1000); }
最新更新: 除了可接受的答案之外,如何LocalCallContext处理还存在差异:即使没有异步,也可以恢复CallContext.LogicalGetData。为什么?
LocalCallContext
主要区别在于 异常传播。 一个例外,内抛出async Task方法,获取存储在返回的Task对象和直到任务被通过观察保持hibernateawait task,task.Wait(),task.Result或task.GetAwaiter().GetResult()。即使从方法的 同步 部分抛出,也以这种方式传播async。
async Task
Task
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
async
考虑以下代码,其中OneTestAsync和的AnotherTestAsync行为完全不同:
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),它将产生以下输出:
DoTestAsync(OneTestAsync, -2)
按Enter继续 错误:发生一个或多个错误。等待Task.Delay 错误:第二
注意,我必须按一下Enter才能看到它。
Enter
现在,如果我调用DoTestAsync(AnotherTestAsync, -2),内部的代码工作流程DoTestAsync将大不相同,输出也将有所不同。这次,我没有被要求按Enter:
DoTestAsync(AnotherTestAsync, -2)
DoTestAsync
错误:该值必须为-1(表示无限超时),0或正整数。 参数名称:millisecondsDelayError:1st
在这两种情况下Task.Delay(-2),在验证其参数时都在开始时抛出。这可能是虚构的情况,但理论上Task.Delay(1000)也可能会抛出异常,例如,当基础系统计时器API发生故障时。
Task.Delay(-2)
Task.Delay(1000)
在一个侧面说明,误差传播逻辑为尚未不同 async void 的方法(而不是async Task方法)。如果当前线程有一个(。,它将async void通过SynchronizationContext.Post)重新抛出方法内部引发的异常(通过)立即在当前线程的同步上下文SynchronizationContext.Current != null)中重新抛出ThreadPool.QueueUserWorkItem。调用者没有机会在同一堆栈帧上处理此异常。
async void
SynchronizationContext.Post
SynchronizationContext.Current != null)
ThreadPool.QueueUserWorkItem
我在这里和这里发布了有关TPL异常处理行为的更多详细信息。
问 :是否可以模仿基于async非异步Task方法的方法的异常传播行为,以使后者不会抛出相同的堆栈帧?
答 :如果确实需要,那么可以,有一个技巧:
// 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仍可以异步执行。
RunSynchronously
另一个显着区别是, 在async/ await版本更容易出现死锁定在一个非默认的同步上下文。例如,以下内容将在WinForms或WPF应用程序中死锁:
await
static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here }
将其更改为非异步版本,不会死锁:
斯蒂芬·克莱里(Stephen Cleary)在他的博客中很好地解释了这种僵局。