Async/Await 最佳做法

来源:互联网 发布:於乎 编辑:程序博客网 时间:2024/04/28 00:54

.NET Framework 4.5 中新增了对 async 和 await 支持,下面来看看如何正确的使用它们或者参考

避免 async void

async 方法有三种返回类型:Task、Task < T > 的 async 方法,任何返回 void 的方法都会成为返回 Task 的 async 方法。

    async Task 或 async Task < T >方法引发异常时,会捕获该异常并将其置于 Task 对象上。 对于 async void 方法,没有 Task 对象,因此 async void 方法引发的任何异常都会直接在 SynchronizationContext(在 async void 方法启动时处于活动状态)上引发。     返回 Task 或 Task < T > 的 async 方法可以使用 await、Task.WhenAny、Task.WhenAll 等方便地组合而成。 返回 void 的 async 方法未提供一种简单方式,用于向调用代码通知它们已完成。  async void 方法会在启动和结束时通知 SynchronizationContext,但是对于常规应用程序代码而言,自定义 SynchronizationContext 是一种复杂的解决方案。

避免死锁

public static class DeadlockDemo{  private static async Task DelayAsync()  {    await Task.Delay(1000);  }  // This method causes a deadlock when called in a GUI or ASP.NET context.public static void Test()  {    // Start the delay.var delayTask = DelayAsync();    // Wait for the delay to complete.delayTask.Wait();  }}
    每个 async 方法都具有自己的上下文,因此如果一个 async 方法调用另一个 async 方法,则其上下文是独立的。    这种死锁的根本原因是 await 处理上下文的方式。 默认情况下,当等待未完成的 Task 时,会捕获当前“上下文”,在 Task 完成时使用该上下文恢复方法的执行。 此“上下文”是当前 SynchronizationContext(除非它是 null,这种情况下则为当前 TaskScheduler)。 GUI 和 ASP.NET 应用程序具有 SynchronizationContext,它每次仅允许一个代码区块运行。 当 await 完成时,它会尝试在捕获的上下文中执行 async 方法的剩余部分。 但是该上下文已含有一个线程,该线程在(同步)等待 async 方法完成。 它们相互等待对方,从而导致死锁。    请注意,控制台应用程序不会形成这种死锁。 它们具有线程池 SynchronizationContext 而不是每次执行一个区块的 SynchronizationContext,因此当 await 完成时,它会在线程池线程上安排 async 方法的剩余部分。 该方法能够完成,并完成其返回任务,因此不存在死锁。 当程序员编写测试控制台程序,观察到部分异步代码按预期方式工作,然后将相同代码移动到 GUI 或 ASP.NET 应用程序中会发生死锁,此行为差异可能会令人困惑。
执行以下操作… 替换以下方式… 使用以下方式 检索后台任务的结果 Task.Wait 或 Task.Result await 等待任何任务完成 Task.WaitAny await Task.WhenAny 检索多个任务的结果 Task.WaitAll await Task.WhenAll 等待一段时间 Thread.Sleep await Task.Delay

配置上下文

随着异步 GUI 应用程序在不断增长,可能会发现 async 方法的许多小部件都在使用 GUI 线程作为其上下文。 这可能会形成迟滞,因为会由于“成千上万的剪纸”而降低响应性。

若要缓解此问题,请尽可能等待 ConfigureAwait 的结果

async Task MyMethodAsync(){  // Code here runs in the original context.await Task.Delay(1000);  // Code here runs in the original context.await Task.Delay(1000).ConfigureAwait(    continueOnCapturedContext: false);  // Code here runs without the original  // context (in this case, on the thread pool).}
0 0