我有3个任务:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
他们都需要在我的代码继续之前运行,我也需要每个代码的结果。没有任何结果彼此有任何共同之处
如何调用并等待3个任务完成然后获得结果?
答案 0 :(得分:302)
使用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
可能会在其他情况下导致问题。
答案 1 :(得分:72)
启动所有这三项任务后,只需await
这三项任务。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
答案 2 :(得分:27)
如果你正在使用C#7,你可以使用像这样的方便的包装方法......
public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
await Task.WhenAll(task1, task2);
return (task1.Result, task2.Result);
}
}
...当您想要等待具有不同返回类型的多个任务时,启用这样的方便语法。当然,您必须为不同数量的任务进行多次重载。
var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
答案 3 :(得分:10)
您可以将它们存储在任务中,然后等待它们:
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;
答案 4 :(得分:8)
鉴于三项任务 - 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
;这个现在存在于Task
的.NET Core中,以及ValueTask<T>
答案 5 :(得分:3)
如果您尝试记录所有错误,请确保在代码中保留Task.WhenAll行,很多注释表明您可以删除它并等待单个任务。 Task.WhenAll对于错误处理非常重要。如果没有这一行,您可能会将代码保留为未观察到的异常。
didreceivepushnotification
想象一下,FeedCat会在以下代码中抛出异常:
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;
在这种情况下,你永远不会等待houseTask或carTask。这里有3种可能的情况:
当FeedCat失败时,SellHouse已成功完成。在 这种情况你很好。
SellHouse不完整,但在某些时候失败。没有观察到异常,将在终结器线程上重新抛出。
SellHouse不完整,包含等待内容。如果 你的代码在ASP.NET中运行SellHouse会尽快失败 等待将在里面完成。这是因为你基本上 制造火灾&amp;忘记呼叫和同步上下文在FeedCat失败后立即丢失。
以下是案例(3)的错误:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
对于情况(2),您将得到类似的错误,但具有原始异常堆栈跟踪。
对于.NET 4.0及更高版本,您可以使用TaskScheduler.UnobservedTaskException捕获未观察到的异常。对于.NET 4.5及更高版本,对于.NET 4.0,默认情况下会吞下未观察到的异常,未观察到的异常会导致进程崩溃。
答案 6 :(得分:2)
您可以使用上述Task.WhenAll
或Task.WaitAll
,具体取决于您是否希望线程等待。请查看链接以获取对两者的解释。
答案 7 :(得分:2)
使用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.
答案 8 :(得分:1)
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());
如果要访问Cat,请执行以下操作:
var ct = (Cat)dn[0];
这是非常简单的操作,并且非常有用,不需要复杂的解决方案。
答案 9 :(得分:0)
前瞻警告
对于那些正在寻找使用async + await +任务工具集并行化EntityFramework的方法的访问者和其他类似线程的快速准备:此处显示的模式是合理的,但是,当它来到EF的特殊雪花你不会实现并行执行,除非你在每个* Async()调用中使用一个单独的(新的)db-context-instance。
由于ef-db-contexts的固有设计限制,这种事情是必要的,它禁止在同一个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);
}
}
答案 10 :(得分:-1)
是不是 await 语句使代码按顺序运行?考虑下面的代码
class Program
{
static Stopwatch _stopwatch = new();
static async Task Main(string[] args)
{
Console.WriteLine($"fire hot");
_stopwatch.Start();
var carTask = BuyCar();
var catTask = FeedCat();
var houseTask = SellHouse();
await carTask;
await catTask;
await houseTask;
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} done!");
Console.WriteLine($"using await");
_stopwatch.Restart();
await BuyCar();
await FeedCat();
await SellHouse();
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} done!");
}
static async Task BuyCar()
{
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} buy car started");
await Task.Delay(2000);
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} buy car done");
}
static async Task FeedCat()
{
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} feed cat started");
await Task.Delay(1000);
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} feed cat done");
}
static async Task SellHouse()
{
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} sell house started");
await Task.Delay(10);
Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} sell house done");
}
}
fire hot
0 buy car started
3 feed cat started
4 sell house started
18 sell house done
1004 feed cat done
2013 buy car done
2014 done!
using await
0 buy car started
2012 buy car done
2012 feed cat started
3018 feed cat done
3018 sell house started
3033 sell house done
3034 done!