一、简介
C# 中的 Task.WhenAll 方法是一种用于并行执行多个任务并等待它们全部完成的高效方式。它接收一个任务数组或任务集合作为参数,并返回一个表示所有任务都已完成的新任务。当调用 Task.WhenAll 返回的任务的 Wait 方法或 Result 属性时,程序会阻塞,直到所有传入的任务都已完成。如果任何一个传入的任务失败并抛出异常,Task.WhenAll 返回的任务也会以异常结束,从而允许开发者集中处理错误。这种方法在需要并行处理多个独立任务并等待它们全部完成时非常有用,能够显著提高应用程序的性能和响应速度。
Task.WhenAll 和 Task.WaitAll 都是 C# 中用于处理并发任务的方法,但它们之间存在显著的不同。以下是对两者的具体比较:
异步与同步
Task.WhenAll:是异步的,不会阻塞调用它的线程。它返回一个表示所有输入任务联合完成的新 Task 对象。
Task.WaitAll:是同步的,会阻塞调用它的线程,直到所有任务都完成为止。它没有返回值。
使用场景
Task.WhenAll:适用于需要并行执行多个任务,并且不想阻塞当前线程的场景。它允许在等待任务完成的同时,执行其他操作。
Task.WaitAll:适用于需要等待所有任务都完成才能继续执行的场景,但它会阻塞当前线程,因此不适合需要保持线程响应性的应用。
异常处理
Task.WhenAll:当任何一个输入任务抛出异常时,它会立即返回一个处于出错状态的 Task 对象。开发者可以通过检查返回的 Task 对象来处理异常。
Task.WaitAll:会一直等待所有任务完成,包括在其中一个任务抛出异常时。异常会被捕获并包装在 AggregateException 中,需要开发者处理这个异常。
返回值与结果获取
Task.WhenAll:返回一个新的 Task 对象,该对象的 Result 属性是一个包含所有输入任务结果的数组(如果输入任务有返回值)。如果输入任务没有返回值,结果数组的元素类型为 void。
Task.WaitAll:没有返回值,它仅用于等待所有任务完成。
官方的文档:
官方的解释:
创建一个任务,该任务将在所有提供的任务完成时完成。
主要接口:
WhenAll(IEnumerable<Task>) | 创建一个任务,该任务将在可枚举集合中的所有 Task 对象完成时完成 |
WhenAll(ReadOnlySpan<Task>) | 创建一个任务,该任务将在所有提供的任务完成时完成 |
WhenAll(Task[]) | 创建一个任务,该任务将在数组中的所有 Task 对象完成时完成 |
WhenAll<TResult>(ReadOnlySpan<Task<TResult>>) | 创建一个任务,该任务将在所有提供的任务完成时完成 |
WhenAll<TResult>(IEnumerable<Task<TResult>>) | 创建一个任务,该任务将在可枚举集合中的所有 Task<TResult> 对象完成时完成 |
WhenAll<TResult>(Task<TResult>[]) | 创建一个任务,该任务将在数组中的所有 Task<TResult> 对象完成时完成 |
WhenAll(IEnumerable<Task>)
创建一个任务,该任务将在可枚举集合中的所有 Task 对象完成时完成。
public static System.Threading.Tasks.Task WhenAll (System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task> tasks);
参数
tasks IEnumerable<Task>
要等待完成的任务。
返回
Task
表示所有提供任务的完成的任务。
例外
ArgumentNullException
tasks 参数 null。
ArgumentException
tasks 集合包含 null 任务。
注解
当对一组任务的状态或一组任务引发的异常感兴趣时,通常会调用返回 Task 对象的 WhenAll 方法的重载。
备注 对 WhenAll(IEnumerable<Task>) 方法的调用不会阻止调用线程。
如果提供的任何任务都以错误状态完成,则返回的任务也将处于 Faulted 状态,其中其异常将包含每个提供的任务的未包装异常集的聚合。
如果提供的任务均未出错,但其中至少一个任务已取消,则返回的任务将以 Canceled 状态结束。
如果没有任何任务出错且未取消任何任务,则生成的任务将以 RanToCompletion 状态结束。
如果提供的数组/可枚举不包含任何任务,则返回的任务将在返回给调用方之前立即转换为 RanToCompletion 状态。
案例
新建一个 winform 项目,建 winform 项目主要是看 Task.WhenAll 执行过程中,是否会卡住 UI 线程,界面暂时没添加任何控件。
代码:
添加一个类 PingTool
using System;
using System.Net.Http;
using System.Threading.Tasks;
internal class PingTool
{
public async Task<bool> Ping(string url)
{
if (string.IsNullOrEmpty(url))
return false;
try
{
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(5);
var response = await client.GetAsync(url);
return response.IsSuccessStatusCode;
}
}
catch (System.Exception)
{
return false;
}
}
}
Form1代码:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WhenAllTest2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Start();
}
private async void Start()
{
List<Task<(string, bool)>> taskList = new List<Task<(string, bool)>>();
taskList.Add(PingTest("https://www.youkuaiyun.com/"));
taskList.Add(PingTest("https://www.zhihu.com/"));
taskList.Add(PingTest("https://www.microsoft.com/zh-cn/"));
taskList.Add(PingTest("https://www.baidu.com/"));
taskList.Add(PingTest("https://github.com/"));
var resultList = await Task.WhenAll(taskList);
foreach (var result in resultList)
{
Console.WriteLine("ping {0} 的结果:{1}", result.Item1, result.Item2);
}
Console.WriteLine("执行完成");
}
private async Task<(string, bool)> PingTest(string ip)
{
PingTool tool = new PingTool();
bool result = await tool.Ping(ip);
return (ip, result);
}
}
}
结果:
在执行的过程中,拖动界面可以看到,几乎没有什么影响。
WhenAll(ReadOnlySpan<Task>)
创建一个任务,该任务将在所有提供的任务完成时完成。
public static System.Threading.Tasks.Task WhenAll (scoped ReadOnlySpan<System.Threading.Tasks.Task> tasks);
参数
tasks ReadOnlySpan<Task>
要等待完成的任务。
返回
Task
表示所有提供任务的完成的任务。
例外
ArgumentException
tasks 数组包含 null 任务。
注解
如果提供的任何任务都以错误状态完成,则返回的任务也将处于“出错”状态,其中异常将包含每个提供任务中未包装的异常集的聚合。
如果提供的任务均未出错,但至少其中一个任务已取消,则返回的任务将以“已取消”状态结束。
如果没有任何任务出错且未取消任何任务,则生成的任务将以 RanToCompletion 状态结束。
如果提供的跨度不包含任何任务,则返回的任务将在返回给调用方之前立即转换为 RanToCompletion 状态。
在 .NET Framework 中,ReadOnlySpan 并不支持
这里我使用 .Net9 做了一个案例
代码:
namespace WhenAllTest3
{
internal class Program
{
static async Task Main(string[] args)
{
// 定义多个异步任务
Task task1 = SimulateTaskAsync("任务1", 2000);
Task task2 = SimulateTaskAsync("任务2", 1000);
Task task3 = SimulateTaskAsync("任务3", 3000);
// 将任务放入一个数组
Task[] tasks = new[] { task1, task2, task3 };
// 使用 ReadOnlySpan<Task>
ReadOnlySpan<Task> taskSpan = new ReadOnlySpan<Task>(tasks);
Console.WriteLine("等待所有任务完成...");
// 等待所有任务完成
await Task.WhenAll(taskSpan);
Console.WriteLine("所有任务已完成。");
Console.ReadKey();
}
// 模拟异步任务
static async Task SimulateTaskAsync(string taskName, int delay)
{
Console.WriteLine($"{taskName} 开始执行。");
await Task.Delay(delay);
Console.WriteLine($"{taskName} 执行完成。");
}
}
}
运行:
WhenAll(Task[])
创建一个任务,该任务将在数组中的所有 Task 对象完成时完成。
public static System.Threading.Tasks.Task WhenAll (params System.Threading.Tasks.Task[] tasks);
参数
tasks Task[]
要等待完成的任务。
返回
Task
表示所有提供任务的完成的任务。
例外
ArgumentNullException
tasks 参数 null。
ArgumentException
tasks 数组包含 null 任务。
注解
当对一组任务的状态或一组任务引发的异常感兴趣时,通常会调用返回 Task 对象的 WhenAll 方法的重载。
备注 对 WhenAll(Task[]) 方法的调用不会阻止调用线程。
如果提供的任何任务都以错误状态完成,则返回的任务也将处于 Faulted 状态,其中其异常将包含每个提供的任务的未包装异常集的聚合。
如果提供的任务均未出错,但其中至少一个任务已取消,则返回的任务将以 Canceled 状态结束。
如果没有任何任务出错且未取消任何任务,则生成的任务将以 RanToCompletion 状态结束。
如果提供的数组/可枚举不包含任何任务,则返回的任务将在返回给调用方之前立即转换为 RanToCompletion 状态。
代码:
namespace WhenAllTest3
{
internal class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始执行任务...");
// 定义多个异步任务
Task task1 = SimulateTaskAsync("任务1", 2000);
Task task2 = SimulateTaskAsync("任务2", 1000);
Task task3 = SimulateTaskAsync("任务3", 3000);
// 将任务放入一个数组
Task[] tasks = { task1, task2, task3 };
// 使用 Task.WhenAll 等待所有任务完成
await Task.WhenAll(tasks);
Console.WriteLine("所有任务已完成!");
}
// 模拟异步任务
static async Task SimulateTaskAsync(string taskName, int delay)
{
Console.WriteLine($"{taskName} 开始执行。");
await Task.Delay(delay);
Console.WriteLine($"{taskName} 执行完成。");
}
}
}
运行:
WhenAll<TResult>(ReadOnlySpan<Task<TResult>>)
创建一个任务,该任务将在所有提供的任务完成时完成。
public static System.Threading.Tasks.Task<TResult[]> WhenAll<TResult> (scoped ReadOnlySpan<System.Threading.Tasks.Task<TResult>> tasks);
类型参数
TResult
参数
tasks ReadOnlySpan<Task<TResult>>
要等待完成的任务。
返回
Task<TResult[]>
表示所有提供任务的完成的任务。
例外
ArgumentException
tasks 数组包含 null 任务。
注解
如果提供的任何任务都以错误状态完成,则返回的任务也将处于“出错”状态,其中异常将包含每个提供任务中未包装的异常集的聚合。
如果提供的任务均未出错,但至少其中一个任务已取消,则返回的任务将以“已取消”状态结束。
如果没有任何任务出错且未取消任何任务,则生成的任务将以 RanToCompletion 状态结束。 返回的任务的结果将设置为一个数组,该数组包含所提供的任务的所有结果,其顺序与提供的顺序相同(例如,如果输入任务数组包含 t1、t2、t3,则输出任务的结果将返回一个 TResult[],其中 arr[0] == t1)。Result, arr[1] == t2。结果和 arr[2] == t3。结果)。
如果提供的数组/可枚举不包含任何任务,则返回的任务将在返回给调用方之前立即转换为 RanToCompletion 状态。 返回的 TResult[] 将是 0 个元素的数组。
在 .NET Framework 中,ReadOnlySpan 并不支持
代码:
namespace WhenAllTest3
{
internal class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始执行任务...");
// 定义多个返回结果的异步任务
Task<int> task1 = SimulateTaskAsync("任务1", 2000, 10);
Task<int> task2 = SimulateTaskAsync("任务2", 1000, 20);
Task<int> task3 = SimulateTaskAsync("任务3", 3000, 30);
// 将任务放入一个数组
Task<int>[] tasks = { task1, task2, task3 };
// 使用 ReadOnlySpan<Task<TResult>>
ReadOnlySpan<Task<int>> taskSpan = new ReadOnlySpan<Task<int>>(tasks);
// 使用 Task.WhenAll 等待所有任务完成并获取结果
int[] results = await Task.WhenAll(taskSpan);
Console.WriteLine("所有任务已完成,结果如下:");
foreach (var result in results)
{
Console.WriteLine($"任务返回结果:{result}");
}
}
// 模拟异步任务,返回一个整数结果
static async Task<int> SimulateTaskAsync(string taskName, int delay, int result)
{
Console.WriteLine($"{taskName} 开始执行,预计耗时 {delay} 毫秒...");
await Task.Delay(delay); // 模拟异步操作
Console.WriteLine($"{taskName} 执行完成,结果:{result}");
return result;
}
}
}
运行:
WhenAll<TResult>(IEnumerable<Task<TResult>>)
创建一个任务,该任务将在可枚举集合中的所有 Task<TResult> 对象完成时完成。
public static System.Threading.Tasks.Task<TResult[]> WhenAll<TResult> (System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task<TResult>> tasks);
类型参数
TResult
已完成任务的类型。
参数
tasks IEnumerable<Task<TResult>>
要等待完成的任务。
返回
Task<TResult[]>
表示所有提供任务的完成的任务。
例外
ArgumentNullException
tasks 参数 null。
ArgumentException
tasks 集合包含 null 任务。
代码:
namespace WhenAllTest3
{
internal class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始执行任务...");
// 定义多个返回结果的异步任务
List<Task<int>> tasks = new List<Task<int>>
{
SimulateTaskAsync("任务1", 2000, 10),
SimulateTaskAsync("任务2", 1000, 20),
SimulateTaskAsync("任务3", 3000, 30)
};
// 使用 Task.WhenAll 等待所有任务完成并获取结果
int[] results = await Task.WhenAll(tasks);
Console.WriteLine("所有任务已完成,结果如下:");
foreach (var result in results)
{
Console.WriteLine($"任务返回结果:{result}");
}
// 进一步处理结果
Console.WriteLine($"任务结果总和:{results.Sum()}");
}
// 模拟异步任务,返回一个整数结果
static async Task<int> SimulateTaskAsync(string taskName, int delay, int result)
{
Console.WriteLine($"{taskName} 开始执行,预计耗时 {delay} 毫秒...");
await Task.Delay(delay); // 模拟异步操作
Console.WriteLine($"{taskName} 执行完成,结果:{result}");
return result;
}
}
}
运行:
WhenAll<TResult>(Task<TResult>[])
创建一个任务,该任务将在数组中的所有 Task<TResult> 对象完成时完成。
public static System.Threading.Tasks.Task<TResult[]> WhenAll<TResult> (params System.Threading.Tasks.Task<TResult>[] tasks);
类型参数
TResult
已完成任务的类型。
参数
tasks Task<TResult>[]
要等待完成的任务。
返回
Task<TResult[]>
表示所有提供任务的完成的任务。
例外
ArgumentNullException
tasks 参数 null。
ArgumentException
tasks 数组包含 null 任务。
代码:
namespace WhenAllTest3
{
internal class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始执行任务...");
// 定义多个返回结果的异步任务
Task<int> task1 = SimulateTaskAsync("任务1", 2000, 10);
Task<int> task2 = SimulateTaskAsync("任务2", 1000, 20);
Task<int> task3 = SimulateTaskAsync("任务3", 3000, 30);
// 将任务放入数组
Task<int>[] tasks = { task1, task2, task3 };
// 使用 Task.WhenAll 等待所有任务完成并获取结果
int[] results = await Task.WhenAll(tasks);
Console.WriteLine("所有任务已完成,结果如下:");
foreach (var result in results)
{
Console.WriteLine($"任务返回结果:{result}");
}
// 进一步处理结果
int sum = 0;
foreach (var result in results)
{
sum += result;
}
Console.WriteLine($"任务结果总和:{sum}");
}
// 模拟异步任务,返回一个整数结果
static async Task<int> SimulateTaskAsync(string taskName, int delay, int result)
{
Console.WriteLine($"{taskName} 开始执行,预计耗时 {delay} 毫秒...");
await Task.Delay(delay); // 模拟异步操作
Console.WriteLine($"{taskName} 执行完成,结果:{result}");
return result;
}
}
}
结果:
结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言
end