.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).
}