给定三个任务 - FeedCat()、SellHouse() 和 BuyCar(),有两种有趣的情况:它们要么全部同步完成(出于某种原因,可能是缓存或错误),要么不同步完成。
假设我们有,从问题:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
现在,一个简单的方法是:
Task.WhenAll(x, y, z);
但是……不方便处理结果;我们通常希望await 表示:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
但这会产生大量开销并分配各种数组(包括params Task[] 数组)和列表(内部)。它有效,但它不是很好的IMO。在许多方面,使用async 操作和只使用await 依次更简单:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
与上面的某些 cmets 不同,使用 await 而不是 Task.WhenAll 对任务的运行方式(并发、顺序等)没有区别。在最高级别,Task.WhenAll 早于对async/await 的良好编译器支持,并且在这些东西不存在时很有用。当您有任意一组任务而不是 3 个离散任务时,它也很有用。
但是:我们仍然存在async/await 为继续生成大量编译器噪音的问题。如果任务可能实际上是同步完成的,那么我们可以通过构建具有异步回退的同步路径来优化它:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
这种“带有异步回退的同步路径”方法越来越普遍,尤其是在同步完成相对频繁的高性能代码中。请注意,如果完成始终是真正异步的,这将毫无帮助。
此处适用的其他内容:
-
在最近的 C# 中,一个常见的模式是 async 回退方法通常作为本地函数实现:
Task<string> DoTheThings() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
-
如果有很多不同的返回值完全同步的事情很有可能发生,则更喜欢ValueTask<T> 而不是Task<T>:
ValueTask<string> DoTheThings() {
async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
ValueTask<Cat> x = FeedCat();
ValueTask<House> y = SellHouse();
ValueTask<Tesla> z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask<string>(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
如果可能,首选IsCompletedSuccessfully 而不是Status == TaskStatus.RanToCompletion;这现在存在于 .NET Core 中的 Task,以及无处不在的 ValueTask<T>