几天前,有位网友特意加我微信,问我关于 Parallel
怎么实现并行等待执行完成的问题,还问这种方式稳不稳。因为当时不在电脑前,也比较忙,就没有详细解答。现在抽空把这个问题整理了一下发出来,希望能和大家一起交流学习,同时我自己也能再深入了解。
其实,微软官方文档对这个问题已经讲得非常详细了(更权威更完整)。
这里我结合代码简单介绍几个常用方法及使用场景。如果写得不够清楚或者有改进的地方,欢迎大家一起来讨论、分享意见!
通过反编译代码,打开后看到 Parallel 类下有一大堆方法,乍一看可能觉得密密麻麻,甚至有些“吓人”。
其实,仔细一看,它们的结构其实差不多,主要就这 5 个核心方法。大部分方法只是根据不同的参数做了重载。
这 5 个方法分别是:Parallel.For
、Parallel.ForAsync
、Parallel.ForEach
、Parallel.ForEachAsync
和 Parallel.Invoke
。下面是它们的对比表格,帮助你更清晰地理解它们之间的区别:
方法 | 描述 | 是否支持异步 | 是否支持并行执行 | 适用场景 |
---|---|---|---|---|
Parallel.For | 用于并行执行一个整数范围内的循环,所有迭代会被分配到多个线程执行。 | 否 | 是 | 适用于并行处理整数范围的循环。 |
Parallel.ForAsync | 异步版本的 Parallel.For ,支持并行执行的同时,允许每个循环迭代执行异步操作。 |
是 | 是 | 适用于需要并行执行异步操作的场景。 |
Parallel.ForEach | 用于并行处理集合中的每个元素,每个元素的处理都在一个单独的线程中执行。 | 否 | 是 | 适用于并行处理集合中的元素。 |
Parallel.ForEachAsync | 异步版本的 Parallel.ForEach ,支持在并行处理集合元素时执行异步操作。 |
是 | 是 | 适用于处理集合元素并需要异步操作的场景。 |
Parallel.Invoke | 同时并行执行多个不相互依赖的任务,每个任务都在单独的线程中执行。 | 否 | 是 | 适用于并行执行多个独立任务的场景。 |
代码仓库:
https://github.com/peijiehuang/HowToUseParallel
下面是我花点时间整理出的 Parallel 使用示例。总体来说,Parallel 只是一个并行任务库,其特点主要包括:
- 同步方法会阻塞主线程,而异步方法则不会。
- 支持参数配置,可以灵活设置并行的超时时间和最大线程数等。
- 其他功能基本开箱即用,学习起来难度不高。
希望这些例子能帮助大家更直观地理解 Parallel 的用法!
namespace HowToUseParallel
{
public class Program
{
static async Task Main(string[] args)
{
// 创建一个取消令牌源
var cts = new CancellationTokenSource();
// 设置取消时间为10秒后(避免一直等待,导致程序卡死)
cts.CancelAfter(10000);
// 配置 ParallelOptions
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 3, // 限制最多并发3个任务
CancellationToken = cts.Token // 设置取消令牌
};
//方法一Parallel.For
ParallelFor(options);
//方法二Parallel.ForAsync
await ParallelForAsync(options);
//方法三Parallel.ForEach
ParallelForEach(options);
//方法四Parallel.ForEachAsync
await ParallelForEachAsync(options);
//方法五Parallel.Invoke
ParallelInvoke(options);
}
private static void ParallelFor(ParallelOptions options)
{
Console.WriteLine("Parallel.For-------------------");
//并发开始
Parallel.For(1, 6, options, i =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 开始");
Thread.Sleep((5 - i) * 1000); // 模拟不同任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 完成 (耗时 {endTime - startTime} 毫秒)");
});
//并发未完成前,会堵塞线程
Console.WriteLine("所有任务完成");
Console.WriteLine("Parallel.For-------------------\n");
}
private static async Task ParallelForAsync(ParallelOptions options)
{
Console.WriteLine("Parallel.ForAsync-------------------");
//并发开始
var forAsync = Parallel.ForAsync(1, 6, options, async (i, cancellationToken) =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 开始");
await Task.Delay((5 - i) * 1000, cancellationToken); // 模拟不同任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 完成 (耗时 {endTime - startTime} 毫秒)");
});
//并发未完成前,会等待线程,可以运行其他方法
Console.WriteLine("Parallel.ForAsync异步不堵塞线程");
await forAsync;
Console.WriteLine("所有任务完成");
Console.WriteLine("Parallel.ForAsync-------------------\n");
}
private static void ParallelForEach(ParallelOptions options)
{
Console.WriteLine("Parallel.ForEach-------------------");
//并发开始
Parallel.ForEach(Enumerable.Range(1, 5), options, i =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 开始");
Thread.Sleep((5 - i) * 1000); // 模拟不同任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 完成 (耗时 {endTime - startTime} 毫秒)");
});
//并发未完成前,会堵塞线程
Console.WriteLine("所有任务完成");
Console.WriteLine("Parallel.ForEach-------------------\n");
}
private static async Task ParallelForEachAsync(ParallelOptions options)
{
Console.WriteLine("Parallel.ForEachAsync-------------------");
//并发开始
var forEachAsync = Parallel.ForEachAsync(Enumerable.Range(1, 5), options, async (i, cancellationToken) =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 开始");
await Task.Delay((5 - i) * 1000); // 模拟不同任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 {i} 完成 (耗时 {endTime - startTime} 毫秒)");
});
//并发未完成前,会等待线程,可以运行其他方法
Console.WriteLine("Parallel.ForEachAsync异步不堵塞线程");
await forEachAsync;
Console.WriteLine("所有任务完成");
Console.WriteLine("Parallel.ForEachAsync-------------------\n");
}
private static void ParallelInvoke(ParallelOptions options)
{
Console.WriteLine("Parallel.Invoke-------------------");
//并发开始
Parallel.Invoke(options,
() =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 1 开始");
Thread.Sleep(5000); // 模拟任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 1 完成 (耗时 {endTime - startTime} 毫秒)");
},
() =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 2 开始");
Thread.Sleep(4000); // 模拟任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 2 完成 (耗时 {endTime - startTime} 毫秒)");
},
() =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 3 开始");
Thread.Sleep(3000); // 模拟任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 3 完成 (耗时 {endTime - startTime} 毫秒)");
},
() =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 4 开始");
Thread.Sleep(2000); // 模拟任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 4 完成 (耗时 {endTime - startTime} 毫秒)");
},
() =>
{
var startTime = DateTime.Now;
Console.WriteLine($"[{startTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 5 开始");
Thread.Sleep(1000); // 模拟任务耗时
var endTime = DateTime.Now;
Console.WriteLine($"[{endTime}] 线程 {Thread.CurrentThread.ManagedThreadId}: 任务 5 完成 (耗时 {endTime - startTime} 毫秒)");
}
);
//堵塞主线程
Console.WriteLine("所有任务完成");
Console.WriteLine("Parallel.Invoke-------------------\n");
}
}
}