我有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个任务完成,然后得到结果?
当前回答
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!
其他回答
The three tasks in your example differ greatly in importance. In case one of them fails, you probably want to know what happened with the others. For example in case the communication with the automatic cat feeder failed, you don't want to miss whether selling your house succeeded or failed. So it makes sense to return back not just a Cat, a House and a Tesla, but the tasks themselves. The calling code will then be able to query separately each of the three tasks, and react appropriately to their successful or failed completions:
public async Task<(Task<Cat>, Task<House>, Task<Tesla>)> FeedCatSellHouseBuyCar()
{
Task<Cat> task1 = FeedCat();
Task<House> task2 = SellHouse();
Task<Tesla> task3 = BuyCar();
// All three tasks are launched at this point.
try { await Task.WhenAll(task1, task2, task3).ConfigureAwait(false); } catch { }
// All three tasks are completed at this point.
return (task1, task2, task3);
}
使用的例子:
var (catTask, houseTask, teslaTask) = await FeedCatSellHouseBuyCar();
// All three tasks are completed at this point.
if (catTask.IsCompletedSuccessfully)
Console.WriteLine($"{catTask.Result.Name} is eating her healthy meal.");
else
Console.WriteLine("Your cat is starving!");
if (houseTask.IsCompletedSuccessfully)
Console.WriteLine($"Your house at {houseTask.Result.Address} was sold. You are now rich and homeless!");
else
Console.WriteLine("You are still the poor owner of your house.");
if (teslaTask.IsCompletedSuccessfully)
Console.WriteLine($"You are now the owner a battery-powered {teslaTask.Result.Name}.");
else
Console.WriteLine("You are still driving a Hyundai.");
带有空catch的try块是必需的,因为. net 7仍然没有提供适当的方法来等待任务,而不会在取消或失败时抛出。
给定三个任务——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>
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!
你可以使用Task。如前所述,WhenAll或Task。WaitAll,这取决于您是否希望线程等待。看看这两个解释的链接。
WaitAll vs WhenAll
提出警告
对于那些访问这个线程和其他类似线程寻找使用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);
}
}