几天前,有位网友特意加我微信,问我关于 Parallel 怎么实现并行等待执行完成的问题,还问这种方式稳不稳。因为当时不在电脑前,也比较忙,就没有详细解答。现在抽空把这个问题整理了一下发出来,希望能和大家一起交流学习,同时我自己也能再深入了解。

其实,微软官方文档对这个问题已经讲得非常详细了(更权威更完整)。

https://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/data-parallelism-task-parallel-library

这里我结合代码简单介绍几个常用方法及使用场景。如果写得不够清楚或者有改进的地方,欢迎大家一起来讨论、分享意见!

通过反编译代码,打开后看到 Parallel 类下有一大堆方法,乍一看可能觉得密密麻麻,甚至有些“吓人”。

其实,仔细一看,它们的结构其实差不多,主要就这 5 个核心方法。大部分方法只是根据不同的参数做了重载。

这 5 个方法分别是:Parallel.ForParallel.ForAsyncParallel.ForEachParallel.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");
        }
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *