我有3个任务:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
它们都需要先运行,然后我的代码才能继续,我也需要它们的结果。没有结果有什么共同点
如何调用并等待3个任务完成然后获得结果?
我有3个任务:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
它们都需要先运行,然后我的代码才能继续,我也需要它们的结果。没有结果有什么共同点
如何调用并等待3个任务完成然后获得结果?
Answers:
使用完后WhenAll
,您可以使用以下命令分别提取结果await
:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
您也可以使用Task.Result
(因为到目前为止,您已经知道它们都已成功完成)。但是,我建议使用,await
因为它显然是正确的,而Result
在其他情况下可能会引起问题。
WhenAll
完全删除;等待者将确保您在完成所有任务之前不会超过3个以后的任务。
Task.WhenAll()
允许以并行模式运行任务。我不明白为什么@Servy建议删除它。没有WhenAll
他们,他们将被逐一运作
catTask
在从返回时,它已经在运行FeedCat
。因此,任何一种方法都行得通-唯一的问题是,您await
是一次要一个,还是全部都想。错误处理略有不同-如果使用Task.WhenAll
,则await
即使它们之一较早失败,也将全部处理。
WhenAll
对操作的执行时间或执行方式没有影响。它只是有任何可能影响结果如何观察。在这种特定情况下,唯一的区别是前两个方法之一的错误将导致在我的方法中比Stephen的方法更早地在此调用堆栈中引发异常(尽管如果有任何错误,总是会引发相同的错误) )。
在await
全部启动之后,仅将这三个任务分开。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
Task.WhenAll
以任何可观察的方式,添加更改几乎不会改变程序的行为。这是一个纯粹的冗余方法调用。如果愿意,欢迎您添加它作为一种美观的选择,但是它不会改变代码的作用。代码的执行时间将是在或不在方法调用相同(当然,技术上也将是一个非常小的调用开销WhenAll
,但是这应该是可以忽略不计),才使得该版本稍微长于这个版本来运行。
WhenAll
只是纯粹的审美变化。行为上唯一可观察到的差异是,如果较早的任务发生故障,您是否等待较晚的任务完成,通常不需要这样做。如果您不相信有关为什么您的陈述不正确的众多解释,则可以直接为自己运行代码,然后查看结果是否正确。
如果您使用的是C#7,则可以使用这样的便捷包装方法...
public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
return (await task1, await task2);
}
}
...以便在您想要等待具有不同返回类型的多个任务时启用此类便利的语法。当然,您必须为要等待的不同数量的任务进行多个重载。
var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
但是,如果您打算将本示例转变为真实的示例,请参阅Marc Gravell的答案,以对ValueTask和已完成的任务进行一些优化。
Task.WhenAll()
没有返回元组。Result
在完成返回的任务之后,将根据提供的任务的属性来构造一个Task.WhenAll()
。
.Result
按照斯蒂芬的推理替换电话,以免其他人通过复制您的示例来使不良做法永久化。
鉴于三个任务- 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);
}
相反,一些上述评论的,使用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>
Task
当它们全部完成时只返回一个a ,而不使用结果。
await
假设例外是罕见但有意义的前提下应该获得“更好的”例外语义
您可以将它们存储在任务中,然后等待所有它们:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
var catTask = FeedCat()
执行该函数FeedCat()
并将结果存储为catTask
使该await Task.WhenAll()
部分无用的部分,因为该方法已经执行了?
如果您试图记录所有错误,请确保在代码中保留Task.WhenAll行,大量注释表明您可以删除它并等待单个任务。Task.WhenAll对于错误处理非常重要。如果没有此行,您可能会为未观察到的异常保留代码。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
想象一下,FeedCat在以下代码中引发异常:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
在这种情况下,您将永远不会等待houseTask或carTask。这里有3种可能的情况:
FeedCat失败时,SellHouse已成功完成。在这种情况下,你很好。
SellHouse尚不完整,并且在某些时候会因异常而失败。未观察到异常,它将在终结器线程上重新引发。
SellHouse不完整,其中包含等待项。如果您的代码在ASP.NET SellHouse中运行,一旦其中的一些等待完成,它就会失败。发生这种情况的原因是,一旦FeedCat发生故障,您基本上就立即开火并忘记了呼叫,并且同步上下文丢失了。
这是针对情况(3)的错误:
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()<---
对于情况(2),您将获得类似的错误,但具有原始异常堆栈跟踪。
对于.NET 4.0及更高版本,您可以使用TaskScheduler.UnobservedTaskException捕获未观察到的异常。对于.NET 4.5和更高版本,.NET 4.0默认会吞没不可观察的异常,不可观察的异常将使您的进程崩溃。
此处有更多详细信息:.NET 4.5中的任务异常处理
您可以使用Task.WhenAll
或所述Task.WaitAll
,取决于您是否希望线程等待。查看链接以了解这两种情况。
使用Task.WhenAll
然后等待结果:
var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar;
//as they have all definitely finished, you could also use Task.Value.
向前警告
对那些访问此线程以及其他类似线程的人们进行快速了解,他们正在寻找一种使用async + await + task工具集并行化EntityFramework的方法:此处显示的模式是合理的,但是,当涉及到EF的特殊功能时,您不会实现并行执行,除非并且直到您在每个涉及的每个* Async()调用中使用单独的(新)db-context-instance为止。
由于ef-db-context的固有设计局限性,这种事情是必需的,它限制了在同一ef-db-context实例中并行运行多个查询。
利用已经给出的答案,这是确保即使在一项或多项任务导致异常的情况下,您也可以收集所有值的方法:
public async Task<string> Foobar() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoSomething(await a, await b, await c);
}
using (var carTask = BuyCarAsync())
using (var catTask = FeedCatAsync())
using (var houseTask = SellHouseAsync())
{
if (carTask.Status == TaskStatus.RanToCompletion //triple
&& catTask.Status == TaskStatus.RanToCompletion //cache
&& houseTask.Status == TaskStatus.RanToCompletion) { //hits
return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
}
cat = await catTask;
car = await carTask;
house = await houseTask;
//or Task.AwaitAll(carTask, catTask, houseTask);
//or await Task.WhenAll(carTask, catTask, houseTask);
//it depends on how you like exception handling better
return Awaited(catTask, carTask, houseTask);
}
}
具有或多或少相同性能特征的替代实现可以是:
public async Task<string> Foobar() {
using (var carTask = BuyCarAsync())
using (var catTask = FeedCatAsync())
using (var houseTask = SellHouseAsync())
{
cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);
return DoSomething(cat, car, house);
}
}