任务和异步之间的区别
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开始。