1.15 并行编程

1.并行循环基本语法

2.并行循环原理

3.并行循环中的异常处理

4.停止

5.中断

6.取消并行循环


1.并行循环基本语法

csharp 复制代码
C#中的Parallel类(位于System.Threading.Tasks命名空间)是.NET提供的并行编程核心工具, 旨在简化"数据并行"和 

"任务并行"开发, 充分利用多核CPU资源, 避免手动管理线程的复杂度; 它的核心目标是将串行执行的任务(如循环、独立方

法)"自动拆分为多个并行任务, 复用线程池线程执行", 提升CPU密集型任务的效率
csharp 复制代码
1).Parallel.For: 并行版for循环

替代传统的串行for循环, 将循环迭代拆分为多个并行任务执行, 适合"遍历连续整数范围"的场景
csharp 复制代码
using System;
using System.Threading.Tasks;

class ParallelForDemo
{
    static void Main()
    {
        int[] data = new int[10000];
        // 初始化数组(串行)
        for (int i = 0; i < data.Length; i++) data[i] = i;

        // 并行遍历数组,每个元素乘以2(CPU密集型)
        Parallel.For(0, data.Length, i =>
        {
            data[i] *= 2;
            // 打印线程ID,验证并行(不同迭代可能在不同线程执行)
            if (i % 1000 == 0)
                Console.WriteLine($"迭代{i},线程ID: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
        });

        Console.WriteLine($"第一个元素: {data[0]}, 最后一个元素: {data[9999]}");
    }
}

csharp 复制代码
2).Parallel.Foreach: 并行版foreach

替代传统的串行foreach, 遍历实现了IEnumerable的集合(如 List、数组、Dictionary), 适合"遍历非连续集合"的场景
csharp 复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class ParallelForEachDemo
{
    static void Main()
    {
        List<string> fruits = new List<string> { "Apple", "Banana", "Orange", "Grape", "Mango" };

        // 并行遍历集合,处理每个元素
        Parallel.ForEach(fruits, fruit =>
        {
            string upperFruit = fruit.ToUpper();
            Console.WriteLine($"处理结果: {upperFruit} (线程ID: {System.Threading.Thread.CurrentThread.ManagedThreadId})");
        });
    }
}

csharp 复制代码
3).Parallel.Invoke: 并行执行多个独立的任务

用于一次性执行多个无返回值、无参数的独立方法, 适合"多任务并行执行"场景
csharp 复制代码
using System;
using System.Threading.Tasks;

class ParallelDemo
{
    static void Main()
    {
        // 并行执行两个独立方法,无需关注执行顺序
        Parallel.Invoke(
            () => CalculateSum(1, 1000000),  // 任务1:计算1到100万的和
            () => PrintMessage("Hello Parallel")  // 任务2:打印信息
        );

        Console.WriteLine("所有并行任务执行完成");
    }

    static void CalculateSum(int start, int end)
    {
        long sum = 0;
        for (int i = start; i <= end; i++) sum += i;
        Console.WriteLine($"Sum: {sum} (线程ID: {System.Threading.Thread.CurrentThread.ManagedThreadId})");
    }

    static void PrintMessage(string msg)
    {
        Console.WriteLine($"{msg} (线程ID: {System.Threading.Thread.CurrentThread.ManagedThreadId})");
    }
}

2.并行循环原理

csharp 复制代码
并行循环的原理是"分块减少调度开销"和"线程复用避免创建成本"

a.数据分区(分块) ------ 不是"均分", 而是"动态按需分块"

并行循环首先会将待处理的数据集(比如0~999的迭代、List集合)拆分为若干"分区(Chunk)", 但不是静态均分, 而是由.NET

的"分区器(Partitioner)"动态调整

- 静态分区(适用于迭代执行时间均匀的场景)

启动前将数据均分(比如1000个迭代, 4核CPU拆成4块, 每块250 个), 优点是分区开销小, 缺点是如果某块迭代执行慢(比如

处理大数据), 会导致"有的线程闲、有的线程忙"(负载不均)

- 动态分区(Parallel默认策略)

不提前均分, 而是"按需分配小批次"(比如每次分配 10~20 个迭代为一个小块), 线程处理完当前小块后, 立刻去"领取"下一

个小块, 直到所有数据处理完

✅优势: 解决负载不均问题(比如某块迭代执行慢, 其他线程不会等, 继续领新块), 最大化CPU利用率

b.线程调度 ------ 复用线程池, 而非创建新线程

Parallel循环不会为每个块创建新线程, 而是复用.NET"线程池(ThreadPool)"的工作线程

线程池默认有"最小线程数(= CPU 核心数)"和"最大线程数(默认 1023)", Parallel会向线程池请求线程, 而非手动创建(避

免线程创建 / 销毁的昂贵开销)

并行度(同时运行的线程数)默认由".NET根据CPU核心数、当前系统负载动态调整"

c.执行与线程复用 ------ 一个线程处理多个块

线程与块不是一一绑定, 一个线程处理完一个小块后, 不会销毁, 而是立刻从分区器领取下一个小块继续执行; 直到所有小块

处理完毕, 线程才会回到线程池, 等待后续复用

d.收尾 ------ 合并结果(如有) + 处理异常

- 若有共享结果(比如累加求和), 需通过原子操作 / 锁 

- 若多个块抛出异常, 会封装为AggregateException统一抛出

3.并行循环中的异常处理

csharp 复制代码
并行循环中的异常不会立即停止本次迭代, 而是停止新的迭代; 将try catch放在并行循环的外面
csharp 复制代码
using System;
using System.Threading.Tasks;

class ParallelExceptionBasic
{
    static void Main()
    {
        try
        {
            // 并行循环:迭代1和3抛出不同异常
            Parallel.For(0, 5, i =>
            {
                Console.WriteLine($"迭代{i}开始执行");
                if (i == 1)
                    throw new ArgumentException($"参数非法:迭代{i}"); // 业务异常1
                if (i == 3)
                    throw new DivideByZeroException($"除零错误:迭代{i}"); // 业务异常2
                Thread.Sleep(100); // 模拟业务逻辑
            });
        }
        // 必须捕获AggregateException,而非单个异常
        catch (AggregateException aggregateEx)
        {
            Console.WriteLine($"捕获到 {aggregateEx.InnerExceptions.Count} 个异常:");
            // 遍历所有内部异常,逐个处理
            foreach (var innerEx in aggregateEx.InnerExceptions)
            {
                // 区分异常类型,针对性处理
                switch (innerEx)
                {
                    case ArgumentException argEx:
                        Console.WriteLine($"参数异常:{argEx.Message}");
                        break;
                    case DivideByZeroException divEx:
                        Console.WriteLine($"除零异常:{divEx.Message}");
                        break;
                    default:
                        Console.WriteLine($"未知异常:{innerEx.Message}");
                        break;
                }
            }
        }
        // 可选:捕获其他非并行循环的异常(比如参数错误)
        catch (Exception ex)
        {
            Console.WriteLine($"非聚合异常:{ex.Message}");
        }
    }
}

4.停止

csharp 复制代码
Stop是"紧急停止" ------ 不管索引顺序, 新迭代全不调度, 已开始的迭代也建议尽快退出(而非执行完)
csharp 复制代码
using System;
using System.Threading.Tasks;

class ParallelBreakVsStop
{
    static void Main()
    {
        Console.WriteLine("=== 测试 Stop() ===");
        var result = Parallel.For(0, 10, (i, state) =>
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 迭代{i} 开始执行");
            
            if (i == 5)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 触发 Stop()");
                state.Stop();
            }

            // 关键:检查IsStopped,尽快退出(Stop的核心)
            if (state.IsStopped)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 迭代{i} 检测到Stop,立即退出");
                return; // 不执行后续的500ms延迟
            }

            // 若没检测IsStopped,才会执行完500ms
            Task.Delay(500).Wait(); 
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 迭代{i} 执行完成");
        });

        Console.WriteLine($"循环是否完成:{result.IsCompleted}");
        Console.WriteLine($"最低中断迭代索引:{result.LowestBreakIteration ?? -1}\n");
    }
}

5.中断

csharp 复制代码
Break是"有序中断" ------ 保证中断点前的迭代全执行完, 只停后面的
csharp 复制代码
using System;
using System.Threading.Tasks;

class ParallelBreakVsStop
{
    static void Main()
    {
        Console.WriteLine("=== 测试 Break() ===");
        var result = Parallel.For(0, 10, (i, state) =>
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 迭代{i} 开始执行");
            
            if (state.ShouldExitCurrentIteration && state.LowestBreakIteration >= index)
			{
			     Console.WriteLine("检测到并行循环中触发Stop, 应立即停止");
			     return;
			}
			
            if (i == 5)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 触发 Break()");
                state.Break();
            }

            // 模拟耗时500ms的业务逻辑(Break下会执行完)
            Task.Delay(500).Wait(); 
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 迭代{i} 执行完成");
        });

        Console.WriteLine($"循环是否完成:{result.IsCompleted}");
        Console.WriteLine($"最低中断迭代索引:{result.LowestBreakIteration}\n");
    }
}

6.取消并行循环

csharp 复制代码
Parallel.For / Parallel.Foreach的取消是通过CancelltionTokenSource控制取消信号的

a.创建CancelltionTokenSource(取消令牌源), 它负责生产CancelltionToken(取消令牌)

b.将CancelltionToken传入ParallelOptions(Parallel的配置参数)

c.当调用CancellationTokenSource.Cancel()时, Parallel会在"合适的时机"(比如每次迭代开始前)检查令牌状态, 若已

取消则抛出OperationCanceledException, 终止并行操作

csharp 复制代码
// 1. 创建取消令牌源(可设置超时自动取消或手动触发)
CancellationTokenSource cts = new CancellationTokenSource();

// 方式A:超时自动取消(比如5秒后自动取消并行操作)
cts.CancelAfter(5000); // 5000ms=5秒

// 方式B:手动触发取消(比如Unity中点击"取消"按钮时调用)
void OnCancelButtonClick() {
    if (!cts.IsCancellationRequested) {
        cts.Cancel(); // 触发取消信号
    }
}
csharp 复制代码
try {
    // 2. 配置Parallel的参数,传入取消令牌
    ParallelOptions options = new ParallelOptions();
    options.CancellationToken = cts.Token; // 绑定取消令牌

    // 3. 执行并行操作(以Parallel.For为例)
    Parallel.For(0, 1000, options, (i, loopState) => 
    {
        if(options.CancellationToken.IsCancellationRequested)
        {
        	return;
        }

        // 你的并行任务逻辑(比如处理数据、加载资源)
        Debug.Log($"处理第{i}项");
        Thread.Sleep(100); // 模拟耗时操作
    });
}
// 步骤3:捕获取消异常,处理取消逻辑
catch (OperationCanceledException ex) {
    Debug.Log($"并行操作已取消:{ex.Message}");
}
catch (AggregateException ex) {
    // Parallel的异常会包装成AggregateException,需展开处理
    foreach (var innerEx in ex.InnerExceptions) {
        if (innerEx is OperationCanceledException) {
            Debug.Log($"并行操作已取消");
        } else {
            Debug.Log($"并行操作出错:{innerEx.Message}");
        }
    }
}
finally {
    // 释放资源
    cts.Dispose();
}
相关推荐
chao1898442 小时前
基于C# WinForm实现的仿微信打飞机游戏
游戏·微信·c#
wearegogog1233 小时前
C# 条码打印程序(一维码 + 二维码)
java·开发语言·c#
sali-tec3 小时前
C# 基于halcon的视觉工作流-章69 深度学习-异常值检测
开发语言·图像处理·算法·计算机视觉·c#
我是唐青枫3 小时前
深入理解 C#.NET 运算符重载:语法、设计原则与最佳实践
开发语言·c#·.net
Lv11770083 小时前
Visual Studio中的字典
ide·笔记·c#·visual studio
helloworddm5 小时前
LocalGrainDirectory详解
c#
武藤一雄6 小时前
.NET 中常见计时器大全
microsoft·微软·c#·.net·wpf·.netcore
Lv11770087 小时前
Visual Studio中Array数组的常用查询方法
笔记·算法·c#·visual studio
wearegogog1237 小时前
基于C#的FTP客户端实现方案
java·网络·c#