我有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个任务完成,然后得到结果?
当前回答
当你使用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在其他场景中可能会导致问题。
其他回答
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!
你可以把它们存储在任务中,然后等待它们:
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;
提出警告
对于那些访问这个线程和其他类似线程寻找使用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);
}
}
给定三个任务——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);
但是…这样不方便处理结果;我们通常会等待:
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[]数组)和列表(内部)。这是可行的,但在我看来并不是很好。在许多方面,使用异步操作并依次等待每个操作会更简单:
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对任务的运行方式(并发、顺序等)没有影响。在最高级别,任务。WhenAll在编译器支持async/await之前就已经存在了,在那些东西不存在的时候非常有用。当你有一个任意的任务数组,而不是3个离散的任务时,它也很有用。
但是:我们仍然有async/await为continuation生成大量编译器噪声的问题。如果有可能这些任务实际上是同步完成的,那么我们可以通过构建带有异步回退的同步路径来优化:
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);
}
这种“同步路径与异步回退”方法越来越普遍,特别是在同步完成相对频繁的高性能代码中。注意,如果补全总是异步的,这一点帮助都没有。
适用于这里的其他事项:
with recent C#, a common pattern is for the async fallback method is commonly implemented as a local function: 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); } prefer ValueTask<T> to Task<T> if there is a good chance of things ever completely synchronously with many different return values: 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); } if possible, prefer IsCompletedSuccessfully to Status == TaskStatus.RanToCompletion; this now exists in .NET Core for Task, and everywhere for ValueTask<T>
当你使用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在其他场景中可能会导致问题。