我有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个任务完成,然后得到结果?
当前回答
提出警告
对于那些访问这个线程和其他类似线程寻找使用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);
}
}
其他回答
你可以使用Task。如前所述,WhenAll或Task。WaitAll,这取决于您是否希望线程等待。看看这两个解释的链接。
WaitAll vs WhenAll
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());
如果你想访问Cat,你可以这样做:
var ct = (Cat)dn[0];
这是非常简单的做法和非常有用的使用,没有必要去追求一个复杂的解决方案。
只需要分别等待这三个任务,在启动它们之后:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
注意:如果任何一个任务抛出异常,这段代码可能会在后面的任务完成之前返回异常,但它们都将运行。在几乎所有的情况下,当你已经知道结果是理想的时候就不要等待。在边缘情况下,可能不是这样。
如果你正在使用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和已经完成的任务的一些优化的回答。
当你使用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。结果(因为此时您知道它们都已成功完成)。但是,我推荐使用await,因为它显然是正确的,而Result在其他场景中可能会导致问题。