这一章解决一个完整问题:
如何正确并发执行多个任务,并统一等待它们完成
本章包含 三个紧密关联的知识点:
1 Task什么时候开始执行
2 并发启动任务的正确方式
3 Task.WhenAll任务组合
这三个知识点是一条完整逻辑链:
任务什么时候启动
↓
如何同时启动多个任务
↓
如何优雅等待它们完成
任务并发与组合
在真实工程中非常常见的需求:
同时调用多个异步操作
等它们全部完成
例如:
加载用户信息
加载订单
加载余额
如果写不好,会导致:
本来100ms
变成300ms
Task什么时候开始执行
很多初学者误解:
await 才会启动任务
实际上:
调用 async 方法时任务就开始了
看代码:
Task t = Task.Delay(2000);
执行流程:
创建Task
↓
定时器开始
↓
2秒后Task完成
注意:
await 不会启动任务
await 只是等待任务
示例
async Task Test()
{
Console.WriteLine("A");
var t = Task.Delay(2000);
Console.WriteLine("B");
await t;
Console.WriteLine("C");
}
执行顺序:
A
B
(2秒)
C
说明:
Task在创建时就开始
如何并发启动任务
现在看一个常见错误写法:
async Task Test()
{
await Task.Delay(1000);
await Task.Delay(1000);
await Task.Delay(1000);
}
执行时间:
≈ 3秒
执行模型:
Delay1
↓
Delay2
↓
Delay3
这是 串行执行。
正确并发方式
async Task Test()
{
var t1 = Task.Delay(1000);
var t2 = Task.Delay(1000);
var t3 = Task.Delay(1000);
await t1;
await t2;
await t3;
}
执行模型:
t1 ┐
t2 ├ 同时开始
t3 ┘
执行时间:≈1秒
原因:任务先启动,await只是等待
Task.WhenAll 任务组合
上面的写法虽然能并发,但代码不优雅:
await t1;
await t2;
await t3;
.NET提供:Task.WhenAll , 作用:等待所有任务完成
示例
async Task Test()
{
var t1 = Task.Delay(2000);
var t2 = Task.Delay(3000);
var t3 = Task.Delay(1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("全部完成");
}
执行时间:≈3秒,因为:t2 最慢
带返回值
Task<int> t1 = GetData(1);
Task<int> t2 = GetData(2);
Task<int> t3 = GetData(3);
int[] results = await Task.WhenAll(t1, t2, t3);
返回:
results[0] -> t1
results[1] -> t2
results[2] -> t3
一个真实工程例子
错误写法:
var user = await GetUser();
var order = await GetOrders();
var balance = await GetBalance();
时间:
100ms + 100ms + 100ms = 300ms
优化:
var userTask = GetUser();
var orderTask = GetOrders();
var balanceTask = GetBalance();
await Task.WhenAll(userTask, orderTask, balanceTask);
时间:≈100ms ,这是 API聚合服务的标准写法。
本章核心模型
这一章其实建立了一个重要的 async 模式:
启动任务
↓
并发执行
↓
统一等待
代码模板:
var t1 = Task1();
var t2 = Task2();
var t3 = Task3();
await Task.WhenAll(t1, t2, t3);
这是 服务器代码中非常常见的结构。
练习1
代码:
async Task Test()
{
var t1 = Task.Delay(2000);
await Task.Delay(2000);
await t1;
}
问题:总时间是多少?为什么?
执行时间线
t=0
创建 t1 (2秒)
t=0
await Task.Delay(2000)
t=2
t1 已完成
Delay 完成
await t1
立即返回
总时间:≈2秒
练习2
代码:
async Task Test()
{
var t1 = Task.Delay(2000);
var t2 = Task.Delay(4000);
await t1;
Console.WriteLine("A");
await t2;
Console.WriteLine("B");
}
问题:
A什么时候打印
B什么时候打印
时间线
t=0
t1开始 (2s)
t2开始 (4s)
t=2
t1完成
打印 A
t=4
t2完成
打印 B
注意关键点:t2 在等待 A 的时候已经执行了 2 秒
练习3(理解并发)
判断下面代码是否真正并发:
await Task.WhenAll(
GetData1(),
GetData2(),
GetData3()
);
问题:任务什么时候开始执行?
执行过程:
调用 GetData1() → Task1开始
调用 GetData2() → Task2开始
调用 GetData3() → Task3开始
然后:WhenAll 等待;
所以:并发发生在调用方法时,不是WhenAll。