任务和异步之间的区别

C#提供了两种创建异步方法的方法:

方法1:

static Task MyAsyncTPL() { Task result = PerformWork(); return result.ContinueWith(t => MyContinuation()); } 

方法2:

 static async Task MyAsync() { string result = await PerformWork(); return MyContinuation(); } 

上述两种方法都是异步的并且实现了同样的目的。 那么,我什么时候应该选择一种方法呢? 是否有使用其中一个的指导方针或优点?

我建议你使用await而不是ContinueWith 。 虽然 – 在高级别 – 它们非常相似,但它们也有不同的默认行为。

使用ContinueWith ,您选择的是较低级别的抽象。 特别是,这里有一些“危险点”,这就是为什么我不建议使用ContinueWith除非方法非常简单 (或者你的名字是Stephen Toub):

  • async Task方法引发的exception放在返回的任务上; 从非async方法引发的exception直接传播。
  • 默认情况下, await将在同一“上下文”中恢复async方法。 这个“上下文”是SynchronizationContext.Current除非它是null ,在这种情况下它是TaskScheduler.Current 。 这意味着如果在UI线程上(或在ASP.NET请求上下文中)调用MyAsync ,则MyContinuation也将在UI线程上执行(或在相同的ASP.NET请求上下文中)。 我在博客上解释了这一点。
  • 您应该始终为ContinueWith指定调度程序; 否则,它将获取TaskScheduler.Current ,这可能会导致令人惊讶的行为。 我在博客上详细描述了这个问题。 StartNew文章是关于StartNew ; 但ContinueWith具有该post中描述的相同“非默认默认调度程序”问题。
  • await使用在ContinueWith默认未设置的适当行为和优化标志。 例如,它使用DenyChildAttach (以确保异步任务不会被错误地用作并行任务)和ExecuteSynchronously (优化)。

简而言之,将ContinueWith用于异步任务的唯一原因是节省少量的时间和内存(通过避免async状态机开销),并且作为交换,您的代码的可读性和可维护性较差。

有一个非常简单的例子,你可能会侥幸逃脱; 但正如Jon Skeet指出的那样,只要你有循环, ContinueWith代码就会在复杂性上爆炸。

await基本上是延续的简写,默认情况下使用相同的同步上下文来表示延续。

对于像你这样的非常简单的例子,使用await没有太大的好处 – 尽管exception的包装和解包使得更加一致的方法。

然而,当你有更复杂的代码时, async会产生巨大的差异。 想象一下你想要的:

 static async Task> MyAsync() { List results = new List(); // One at a time, but each asynchronously... for (int i = 0; i < 10; i++) { // Or use LINQ, with rather a lot of care :) results.Add(await SomeMethodReturningString(i)); } return results; } 

......手动延续会变得更加毛茸茸。

此外, async / await可以使用Task / Task以外的类型,只要它们实现适当的模式即可。

值得一读的更多关于它在幕后做的事情。 您可能想从MSDN开始。