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



