在 C# 的并行编程中,自定义分区器是提升 Parallel.ForEach 等并行操作性能的重要手段。本文将从零开始,手把手教你如何创建和优化自定义分区器,即使你是编程小白也能轻松掌握!

在 .NET 中,System.Collections.Concurrent.Partitioner 类用于将数据源划分为多个“块”(chunks),供多个线程并行处理。默认情况下,.NET 会为数组、列表等集合自动选择合适的分区策略。
但当你的数据访问模式不规则、处理时间差异大,或需要精细控制负载均衡时,C# 自定义分区器就派上用场了。
默认分区器对连续内存结构(如数组)效果很好,但面对以下场景可能表现不佳:
通过实现 OrderablePartitioner<T> 或 Partitioner<T>,你可以完全控制数据如何被分割和分配,从而显著提升并行编程性能优化效果。
下面是一个基于“动态块大小”的自定义分区器示例。它适用于处理时间不均的任务——任务越靠后,块越小,从而让空闲线程能更快接手剩余工作。
using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Threading.Tasks;public class DynamicChunkPartitioner<T> : Partitioner<T>{ private readonly IList<T> _source; public DynamicChunkPartitioner(IList<T> source) { _source = source ?? throw new ArgumentNullException(nameof(source)); } public override bool SupportsDynamicPartitions => true; public override IList<ILookup<int, T>> GetPartitions(int partitionCount) { // 不推荐用于动态分区,通常返回 null 或抛异常 throw new NotSupportedException(); } public override IEnumerable<T> GetDynamicPartitions() { return new DynamicPartitionEnumerator(_source); } private class DynamicPartitionEnumerator : IEnumerable<T>, IEnumerator<T> { private readonly IList<T> _list; private int _index = -1; private readonly object _lock = new object(); public DynamicPartitionEnumerator(IList<T> list) => _list = list; public T Current { get; private set; } object System.Collections.IEnumerator.Current => Current; public bool MoveNext() { lock (_lock) { if (_index + 1 >= _list.Count) return false; _index++; Current = _list[_index]; return true; } } public void Reset() => throw new NotSupportedException(); public void Dispose() { } public IEnumerator<T> GetEnumerator() => this; System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => this; }}这个分区器每次只返回一个元素,确保负载极度均衡(适合处理时间差异大的场景)。虽然加锁有开销,但在任务本身耗时较长时,这点开销可以忽略。
现在,我们将上面的分区器用于 Parallel.ForEach,并对比默认行为:
// 模拟处理时间不均的任务var tasks = Enumerable.Range(1, 100).Select(i => new { Id = i, Delay = i % 10 == 0 ? 500 : 10 } // 每第10个任务很慢).ToList();// 默认方式(静态分区,可能导致负载不均)Parallel.ForEach(tasks, item =>{ Thread.Sleep(item.Delay); Console.WriteLine($"处理 {item.Id} 完成");});// 使用自定义分区器(动态分区,负载更均衡)var partitioner = new DynamicChunkPartitioner<var>(tasks);Parallel.ForEach(partitioner, item =>{ Thread.Sleep(item.Delay); Console.WriteLine($"处理 {item.Id} 完成");});你会发现,使用自定义分区器后,总执行时间明显缩短——因为慢任务不会“拖累”整个并行过程。
其实,.NET 提供了 Partitioner.Create 方法,可快速创建带“块大小”控制的分区器,无需从头实现:
// 将列表按每块 5 个元素分区var simplePartitioner = Partitioner.Create(myList, chunkSize: 5);Parallel.ForEach(simplePartitioner, chunk =>{ foreach (var item in chunk) { // 处理 item }});这种方式适合你知道大致最优块大小的场景,是 Partitioner.Create 自定义的轻量级方案。
在实际项目中,请务必进行性能测试:
Stopwatch 测量不同分区策略的总耗时记住:C# Parallel.ForEach 优化的核心在于“平衡”——平衡负载、平衡开销与收益。
自定义分区器是 C# 并行编程中的高级技巧,但掌握它能让你的应用性能更上一层楼。无论是通过继承 Partitioner<T> 实现完全控制,还是使用 Partitioner.Create 快速调整块大小,关键在于理解你的数据和任务特性。
希望这篇教程能帮你掌握 C# 自定义分区器的核心思想,并在实际项目中实现高效的并行编程性能优化!
本文由主机测评网于2025-12-10发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/2025125468.html