You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:14:57