并行循环

本文介绍并行库TaskParallelLibrary在BCL中的应用,通过Parallel.For和Parallel.ForEach简化并行编程,实现不同处理器上的并行处理,提高程序运行效率。

并行库(Task Parellel Library)是BCL中的一个类库,它极大地简化了并行编程,其中有俩个简单的结构为Parallel.ForParallel.ForEach循环,这俩个结构位于System.Threading.Tasks命名空间中。之前的for、foreach循环中,每一次迭代会依赖于之前那一次迭代的计算行为,而这俩个循环是迭代之间彼此独立,可以将不同的迭代放在不同的处理器上并行处理。

Parallel.For

该方法有12个重载,最简单的签名如下:

        static void Main(string[] args)
        {
            Parallel.For(0, 15, i => Console.WriteLine("The square of {0} is {1}", i, i * i));
        }
The square of 0 is 0
The square of 1 is 1
The square of 6 is 36
The square of 9 is 81
The square of 10 is 100
The square of 11 is 121
The square of 13 is 169
The square of 14 is 196
The square of 4 is 16
The square of 5 is 25
The square of 3 is 9
The square of 12 is 144
The square of 7 is 49
The square of 8 is 64
The square of 2 is 4

该程序满足各个迭代之间是相互独立的,并放在不同处理器上进行处理,所以不能确保迭代的执行次序。

Parallel.ForEach

该方法有相当多的重载,其中最简单的如下:

     static void Main(string[] args)
        {
            string[] squares = new string[]
            {
                "We","hold","these","truths","to"
            };
            Parallel.ForEach(squares, i => Console.WriteLine(string.Format("{0} has {1} letters", i, i.Length)));
        }

 

<think> 我们被要求提供关于C#并行循环的使用方法、示例及注意事项。根据提供的引用,特别是引用[1]、[4]和[5],我们可以总结如下: 1. 使用方法: - 使用`Parallel.For`和`Parallel.ForEach`来处理循环并行执行。 - 这些方法属于`System.Threading.Tasks`命名空间,需要引用`System.Threading.Tasks`程序集。 2. 示例: - 引用[5]中提供了一个简单的`Parallel.For`示例:计算0到49的平方并存储到数组中,且保持索引顺序。 ```csharp int[] squares = new int[50]; Parallel.For(0, 50, i => squares[i] = i * i); ``` - 另一个示例是`Parallel.ForEach`,用于并行处理集合中的每个元素。 3. 注意事项: - 线程安全:并行循环中多个任务可能同时访问共享资源,需要使用锁或线程安全集合(引用[1])。 - 避免过度并行:过多的并行度可能导致资源争用和性能下降(引用[1])。 - 异常处理:并行循环中的异常会被聚合,使用`AggregateException`捕获(引用[5])。 - 注意顺序:默认情况下,执行顺序是不确定的,但结果数组的索引顺序会被保持(引用[5])。 - 任务类型:适用于CPU密集型任务(引用[4])。 - 避免阻塞操作:在并行循环中避免阻塞操作,以免浪费线程资源(引用[1])。 根据以上信息,我们可以组织回答如下: ### C#并行循环的使用方法、示例及注意事项 在C#中,并行循环主要通过`Parallel.For`和`Parallel.ForEach`方法实现,它们位于`System.Threading.Tasks`命名空间中,能够将循环迭代分布到多个线程上执行,以充分利用多核处理器的性能。 #### 使用方法 1. **`Parallel.For`**: 用于并行执行for循环。 ```csharp Parallel.For(int fromInclusive, int toExclusive, Action<int> body); ``` - `fromInclusive`: 循环开始索引(包含) - `toExclusive`: 循环结束索引(不包含) - `body`: 每个迭代要执行的操作,接受当前索引作为参数。 2. **`Parallel.ForEach`**: 用于并行处理集合中的每个元素。 ```csharp Parallel.ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body); ``` #### 示例 1. **`Parallel.For` 示例** 计算0到49的平方并存储到数组中(保持索引顺序): ```csharp int[] squares = new int[50]; Parallel.For(0, 50, i => { squares[i] = i * i; }); ``` 结果数组为:`[0, 1, 4, 9, ..., 2401]`[^5]。 2. **`Parallel.ForEach` 示例** 并行处理一个整数列表,计算每个数的平方: ```csharp List<int> numbers = Enumerable.Range(0, 50).ToList(); Parallel.ForEach(numbers, num => { // 注意:如果直接修改集合元素,需要确保线程安全 // 这里我们只是计算,然后可以存储到其他结构中 int square = num * num; // 如果需要存储,可以使用线程安全的集合或使用锁 }); ``` #### 注意事项 1. **线程安全**: 多个线程可能同时访问共享资源(如数组、集合),必须使用锁(`lock`)、本地状态或线程安全集合(如`ConcurrentBag<T>`)来避免竞态条件[^1][^4]。 2. **避免过度并行**: 并行循环适合CPU密集型任务,但对于I/O密集型任务,使用异步编程(如`async/await`)更合适。过度并行可能导致线程争用和性能下降[^1][^4]。 3. **异常处理**: 并行循环中抛出的异常会被封装在`AggregateException`中,需要捕获并处理: ```csharp try { Parallel.For(0, 50, i => { // 可能抛出异常的操作 }); } catch (AggregateException ae) { foreach (var e in ae.InnerExceptions) { // 处理每个异常 } } ``` 4. **顺序问题**: 迭代的执行顺序是随机的(由线程调度决定),但`Parallel.For`在写入数组等索引集合时,索引的顺序会被保持(如示例中`squares[i]`按索引顺序写入)[^5]。若需要保持原始顺序,可以使用`Parallel.For`的索引特性,或使用`AsOrdered()`(在PLINQ中)。 5. **性能优化**: - 使用`ParallelOptions`调整最大并行度(`MaxDegreeOfParallelism`)以避免资源争用。 - 对于包含异步操作的循环,使用`Parallel.ForEachAsync`(.NET 6+)[^2]。 6. **适用场景**: 并行循环适用于可分解的独立计算任务(如图像处理、数值计算)。对于有依赖关系或需要顺序执行的任务,不适合使用并行循环[^4]。 通过上述方法,我们可以有效地利用多核处理器提高CPU密集型任务的执行效率。 §§相关问题§§ 1. 在C#中,如何处理并行循环中的共享变量访问? 2. 如何控制并行循环中的最大并发数? 3. 并行循环和PLINQ有什么区别和联系? </think> ### C#并行循环的使用方法、示例及注意事项 #### 一、核心方法 1. **`Parallel.For`** 用于并行化索引循环,适用于数组或索引可枚举的数据: ```csharp int[] squares = new int[50]; Parallel.For(0, 50, i => { squares[i] = i * i; // 自动保持索引顺序 }); // 输出:[0, 1, 4, 9 ... 2401] [^5] ``` 2. **`Parallel.ForEach`** 用于并行处理集合元素: ```csharp List<string> files = GetFileList(); Parallel.ForEach(files, file => { ProcessFile(file); // 并行处理每个文件 }); ``` 3. **`Parallel.ForEachAsync`** (.NET 6+) 处理包含异步操作的并行循环: ```csharp await Parallel.ForEachAsync(urls, async (url, ct) => { var data = await httpClient.GetStringAsync(url); ProcessData(data); }); ``` #### 二、关键特性 1. **自动负载均衡** 系统自动分配任务到线程池线程,无需手动管理线程[^1]。 2. **索引顺序保持** `Parallel.For` 保持结果数组的原始索引顺序,即使执行顺序随机[^5]。 3. **异常聚合** 所有异常封装在 `AggregateException` 中统一处理: ```csharp try { Parallel.For(0, 100, i => { /* 可能抛出异常的操作 */ }); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) Console.WriteLine(ex.Message); } ``` #### 三、使用注意事项 1. **适用场景** - ✅ **CPU密集型任务**:矩阵计算、图像处理[^4] - ⚠️ **避免I/O密集型**:优先使用 `async/await` 避免线程阻塞[^2][^4] 2. **线程安全要求** ```csharp int counter = 0; Parallel.For(0, 1000, i => { Interlocked.Increment(ref counter); // 必须使用原子操作 }); ``` 3. **避免过度并行** 通过 `ParallelOptions` 控制并发度: ```csharp var options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; Parallel.For(0, 100, options, i => { /* 限制最多4线程 */ }); ``` 4. **资源争用解决方案** - 使用 `ConcurrentBag<T>` 等线程安全集合 - 对于文件/网络操作,采用 `SemaphoreSlim` 限制并发量[^2] #### 四、性能最佳实践 1. **减少同步操作** 优先使用线程本地状态避免锁竞争: ```csharp long total = 0; Parallel.For(0, 1000, () => 0L, // 初始化本地状态 (i, state, local) => local + i, // 本地计算 local => Interlocked.Add(ref total, local) // 合并结果 ); ``` 2. **与异步编程结合** I/O密集型混合场景: ```csharp await Parallel.ForEachAsync(urls, new ParallelOptions { MaxDegreeOfParallelism = 10 }, async (url, ct) => { var data = await DownloadAsync(url); Process(data); } ); ``` 3. **避免嵌套并行** 嵌套使用可能导致线程饥饿: ```csharp // 错误示范(过度并行) Parallel.For(0, 10, i => { Parallel.For(0, 100, j => { ... }); // 可能创建数百线程 }); ``` > **关键原则**:并行循环适用于独立、无状态的任务单元,对共享资源的访问必须同步[^1][^4]。对于数据依赖强的场景,考虑改用PLINQ或任务协作模式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值