如何用2个及以上BackgroundWorker并行遍历列表并获取唯一值?
多BackgroundWorker线程安全遍历列表(无重复取值)
当然可以实现!核心是要给你的共享列表加个线程安全的“取号机制”,让多个BackgroundWorker每次获取元素时不会拿到重复的值——毕竟多线程环境下直接操作共享资源,很容易触发“竞态条件”,导致两个线程同时取到同一个索引的元素。
下面给你两种实用的实现方案,都是C#环境下的(毕竟你用的是List<int>和BackgroundWorker):
方案1:使用lock实现线程安全取值
lock是最直观的线程同步方式,它能保证同一时间只有一个线程进入代码块,完美避免多个线程抢同一个元素的问题。
步骤1:定义共享变量
首先在类级别定义需要共享的列表、索引计数器和锁对象:
private List<int> _ints = new List<int> {1,2,3,4,5,6,7,8,9,10}; private int _currentIndex = 0; private readonly object _lockObj = new object(); // 专用锁对象,避免用this或其他公共对象
步骤2:编写BackgroundWorker的DoWork事件逻辑
所有BackgroundWorker共用同一个DoWork处理方法,逻辑里通过lock来安全获取下一个元素:
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e) { var worker = sender as BackgroundWorker; if (worker == null) return; while (true) { int currentValue = -1; // 锁块:保证只有一个线程能执行这里的索引读取和递增操作 lock (_lockObj) { if (_currentIndex >= _ints.Count) { break; // 所有元素都处理完了,退出循环 } currentValue = _ints[_currentIndex]; _currentIndex++; } // 输出结果(这里用ReportProgress是为了在UI线程安全显示,避免跨线程操作控件) worker.ReportProgress(0, $"{worker.Name} => output {currentValue}"); // 模拟耗时操作(如果你的实际操作很快,加这个能更明显看到多线程分配的效果) System.Threading.Thread.Sleep(100); } }
步骤3:绑定进度事件(可选,用于UI输出)
如果需要在UI上显示结果,绑定ProgressChanged事件(BackgroundWorker会自动切换回UI线程):
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // 可以把结果输出到控制台、TextBox或其他UI控件 Console.WriteLine(e.UserData.ToString()); }
步骤4:启动三个BackgroundWorker
初始化并启动所有后台线程:
// 创建三个BackgroundWorker实例,开启进度报告功能 var worker1 = new BackgroundWorker { Name = "backgroundworker1", WorkerReportsProgress = true }; var worker2 = new BackgroundWorker { Name = "backgroundworker2", WorkerReportsProgress = true }; var worker3 = new BackgroundWorker { Name = "backgroundworker3", WorkerReportsProgress = true }; // 绑定事件 worker1.DoWork += BackgroundWorker_DoWork; worker1.ProgressChanged += BackgroundWorker_ProgressChanged; worker2.DoWork += BackgroundWorker_DoWork; worker2.ProgressChanged += BackgroundWorker_ProgressChanged; worker3.DoWork += BackgroundWorker_DoWork; worker3.ProgressChanged += BackgroundWorker_ProgressChanged; // 启动线程 worker1.RunWorkerAsync(); worker2.RunWorkerAsync(); worker3.RunWorkerAsync();
方案2:使用Interlocked类实现轻量级原子操作
如果你觉得lock有点重(其实对于这种简单场景性能差异可以忽略),可以用Interlocked类的原子操作来实现,它专门处理多线程下的简单数值操作,性能更优。
修改DoWork事件逻辑即可,不需要锁对象:
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e) { var worker = sender as BackgroundWorker; if (worker == null) return; int index; // Interlocked.Increment是原子操作,保证多个线程调用时不会出现索引重复 while ((index = Interlocked.Increment(ref _currentIndex) - 1) < _ints.Count) { var currentValue = _ints[index]; worker.ReportProgress(0, $"{worker.Name} => output {currentValue}"); System.Threading.Thread.Sleep(100); } }
这里的原理是:Interlocked.Increment会以线程安全的方式把_currentIndex加1,返回递增后的值,我们减1就能拿到当前要处理的元素索引,直到索引超出列表长度就退出循环。
关键注意事项
- 你的
List<int>如果是只读的(只是遍历不修改),那不需要额外处理;如果遍历过程中列表会被修改,那还要给列表加线程安全的包装(比如用ConcurrentBag或者自己加锁)。 - 如果你不需要和UI交互,也可以直接在DoWork里用
Console.WriteLine输出,但要注意:Console.WriteLine本身是线程安全的,但其他非线程安全的操作(比如直接修改UI控件)必须通过ReportProgress或者Invoke切换到UI线程执行。 - 确保
_currentIndex是类级别变量,不能是局部变量,否则每个线程会有自己的计数器,就会重复取值了。
这样运行后,你就能看到三个BackgroundWorker各自输出不同的元素,不会出现重复的情况啦!
内容的提问来源于stack exchange,提问作者annajulia
相关产品推荐
相关产品推荐

