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

关于ConcurrentBag<T>中TryTake窃取元素行为的理解误区纠正

纠正对ConcurrentBag窃取元素逻辑的理解偏差

在讲解ConcurrentBag<T>时,曾看到这样的描述:

...准确来说,调用Take会获取当前线程最近添加的元素;若当前线程无元素,则随机获取其他线程最近添加的元素。

但实际查看Visual Studio中TrySteal方法(TryTake内部调用该方法)的注释,逻辑是:

若当前线程无本地队列,则从头队列开始尝试从每个队列窃取元素,直到获取结果。若有本地队列,则从其下一个队列开始遍历,回到当前队列时停止(不包含自身)。

测试代码

using static System.Console;
using System.Collections.Concurrent;

IProducerConsumerCollection<Car> cars = new ConcurrentBag<Car>();

var addBlackCars = Task.Run(ProcessBlackCarModels);
var addNonBlackCars = Task.Run(ProcessNonBlackCarModels);
Task.WaitAll(addBlackCars, addNonBlackCars);
WriteLine($"At present, the repository contains {cars.Count} car(s).");
void ProcessNonBlackCarModels()
{
    Car car;
    car = new("Hyundai Creta", "Pearl");
    WriteLine($"Adding: {car} using task-{Task.CurrentId}");
    cars.TryAdd(car);
    Thread.Sleep(1000);

    car = new("Maruti Suzuki Alto 800", "Red");
    WriteLine($"Adding: {car} using task-{Task.CurrentId}");
    cars.TryAdd(car);
    Thread.Sleep(1000);

    car = new("Toyota Fortuner Avant", "Bronze");
    WriteLine($"Adding: {car} using task-{Task.CurrentId}");
    cars.TryAdd(car);
    Thread.Sleep(1000);

    WriteLine($"Task-{Task.CurrentId} will try removing one item now.");
    if (cars.Count > 0)
    {
        cars.TryTake(out Car removeCar);
        WriteLine($"Tried removing: {removeCar} using task-{Task.CurrentId}");
    }
}

void ProcessBlackCarModels()
{
    Car car;
    car = new("Toyota Fortuner Attitude", "Black");
    WriteLine($"Adding: {car} using task-{Task.CurrentId}");
    cars.TryAdd(car);
    Thread.Sleep(1000);

    car = new("Hyundai Creta Abyss", "Black");
    WriteLine($"Adding: {car} using task-{Task.CurrentId}");
    cars.TryAdd(car);


    // Putting a relatively long sleep so that the other task can finish in between.
    Thread.Sleep(5000);

    WriteLine($"Task-{Task.CurrentId} will try removing three items now.");

    for (int i = 0; i < 3; i++)
    {
        if (cars.Count > 0)
         {
            cars.TryTake(out Car removeCar);
            WriteLine($"Tried removing: {removeCar} using task-{Task.CurrentId}");
        }
    } 

}

// Using primary constructor
class Car(string model, string color)
{
    private string _model = model;
    private string _color = color;

    public override string ToString()
    {
        return $"[{_model}, {_color}]";
    }
}

示例输出

Adding: [Toyota Fortuner Attitude, Black] using task-1
Adding: [Hyundai Creta, Pearl] using task-2
Adding: [Maruti Suzuki Alto 800, Red] using task-2
Adding: [Hyundai Creta Abyss, Black] using task-1
Adding: [Toyota Fortuner Avant, Bronze] using task-2
Task-2 will try removing one item now.
Tried removing: [Toyota Fortuner Avant, Bronze] using task-2
Task-1 will try removing three items now.
Tried removing: [Hyundai Creta Abyss, Black] using task-1
Tried removing: [Toyota Fortuner Attitude, Black] using task-1
Tried removing: [Hyundai Creta, Pearl] using task-1
At present, the repository contains 1 car(s).

理解偏差纠正

从输出可以看到,task-1在窃取第三个元素时,移除了task-2最早添加的元素[Hyundai Creta, Pearl],而非最近添加的[Maruti Suzuki Alto 800, Red]。

ConcurrentBag<T>的核心逻辑是:

  • 每个线程拥有独立的本地元素队列,线程自身取元素时遵循**后进先出(LIFO)**规则,优先拿自己最近添加的元素;
  • 当线程需要窃取其他线程的元素时,会遍历目标线程的队列,并从队列**头部(最早添加的元素)获取,遵循先进先出(FIFO)**规则。

之前的描述简化了窃取逻辑,实际并非“随机获取其他线程最近添加的元素”,而是按固定顺序遍历其他线程队列,并从队列头部窃取元素。这就是为什么task-1窃取task-2的元素时,先拿到的是task-2最早添加的元素,而task-2自己取元素时拿到的是最近添加的元素。

内容的提问来源于stack exchange,提问作者Vaskaran Sarcar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:20:58