C#提供了两种创建异步方法的方法:
方法1:
static Task<string> MyAsyncTPL() {
Task<string> result = PerformWork();
return result.ContinueWith(t => MyContinuation());
}
方法2:
static async Task<string> MyAsync() {
string result = await PerformWork();
return MyContinuation();
}
上述两种方法都是异步的并且实现了同样的目的。那么,我什么时候应该选择一种方法呢?是否有使用其中一个的指导方针或优点?
答案 0 :(得分:17)
我建议您使用await
而不是ContinueWith
。虽然 - 在高层次 - 它们非常相似,但它们也有不同的默认行为。
使用ContinueWith
时,您选择的是较低级别的抽象。特别是,这里有一些“危险点”,这就是为什么我不建议使用ContinueWith
,除非方法非常简单(或者你的名字是Stephen Toub):
async Task
方法引发的异常放在返回的任务上;非async
方法引发的异常会直接传播。await
将在同一“上下文”中恢复async
方法。此“上下文”为SynchronizationContext.Current
,除非它是null
,在这种情况下它是TaskScheduler.Current
。这意味着如果在UI线程上(或在ASP.NET请求上下文中)调用MyAsync
,那么MyContinuation
也将在UI线程上执行(或在相同的ASP.NET请求上下文中) 。我更多地解释了on my blog。ContinueWith
指定调度程序;否则,它会拾取TaskScheduler.Current
,这可能会导致令人惊讶的行为。我详细描述了这个问题on my blog。该帖子约为StartNew
;但是ContinueWith
在该帖子中描述了相同的“非默认默认调度程序”问题。await
使用ContinueWith
中默认未设置的相应行为和优化标志。例如,它使用DenyChildAttach
(以确保异步任务不会被错误地用作并行任务)和ExecuteSynchronously
(优化)。简而言之,将ContinueWith
用于异步任务的唯一原因是保存极其少量的时间和内存(通过避免async
状态机开销) ,作为交换,您的代码不太可读和可维护。
用一个非常简单的例子,你可能会侥幸逃脱;但正如Jon Skeet指出的那样,只要你有循环,ContinueWith
代码就会在复杂性方面爆炸。
答案 1 :(得分:9)
await
基本上是延续的简写,默认情况下使用相同的同步上下文来继续。
对于像你这样的非常简单的例子,使用await
时没有很多的好处 - 尽管异常的包装和解包会产生更一致的方法。
但是,当您获得更复杂的代码时,async
会产生巨大的差异。想象一下你想要的:
static async Task<List<string>> MyAsync() {
List<string> results = new List<string>();
// 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<T>
以外的其他类型,只要它们实现适当的模式。
值得更多地了解它在幕后所做的事情。您可能希望从MSDN开始。