ConcurrentBag TryTake method, which item is removed?
Asked Answered
S

1

1

With reference from MSDN ConcurrentBag<T>::TryTake method.

Attempts to remove and return an object from the ConcurrentBag<T>.

I am wondering about on which basis it removes object from the Bag, as per my understanding dictionary add and remove works on the basis of HashCode.

If concurrent bag has nothing to do with the hashcode, what will happen when the object values get change during the add and remove.

For example:

public static IDictionary<string, ConcurrentBag<Test>> SereverConnections
    = new ConcurrentDictionary<string, ConcurrentBag<Test>>();

public class Student
{
    public string FirstName { get; set; }
    Student student = new Student();
    SereverConnections.Add(student.FirstName{"bilal"});
}

// have change the name from student.FirstName="khan";
// property of the object has been changed

Now the object properties values has been changed.

What will be the behavior of when I remove ConcurrentBag<T>::TryTake method? how it will track the object is same when added?

Code:

public class Test
{
    public HashSet<string> Data = new HashSet<string>();
    public static IDictionary<string, ConcurrentBag<Test>> SereverConnections
        = new ConcurrentDictionary<string, ConcurrentBag<Test>>();

    public string SessionId { set; get; }
    public Test()
    {
        SessionId = Guid.NewGuid().ToString();
    }
    public void Add(string Val)
    {
        lock (Data) Data.Add(Val);
    }

    public void Remove(string Val)
    {
        lock (Data) Data.Remove(Val);
    }

    public void AddDictonary(Test Val)
    {
        ConcurrentBag<Test> connections;
        connections = SereverConnections["123"] = new ConcurrentBag<Test>();

        connections.Add(this);
    }

    public void RemoveDictonary(Test Val)
    {
        SereverConnections["123"].TryTake(out Val);
    }

    public override int GetHashCode()
    {
        return SessionId.GetHashCode();
    }
}

//calling
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();

        test.AddDictonary(test);

        test.RemoveDictonary(test);//remove test.
        test.RemoveDictonary(new Test());//new object
    }
}
Skippie answered 22/8, 2017 at 5:36 Comment(8)
Have you tried it and checked what the behaviour was?Escobedo
I have tried GetHashCode() does not call during add and remove?Skippie
But have you tried the result: adding only one object to the bag, changingits value(s) and then call TryTake(). There you will have your answer...Escobedo
Your code wouldn't compile and there's no TryTake nor is anything added to any ConcurrentBag so it's impossible to know what you meanKatharinakatharine
A ConcurrentBag has no relationship, itself, with any dictionary-like data structure. So I'm really not sure what you're trying to ask here. TryTake attempts to remove an object from the bag. Neither TryTake nor Add have any need to "inspect" the object more thoroughly (such as via GetHashCode). They can just treat the objects as opaque "things" that they take in or hand out.Enterectomy
You have wrong code, ConcurrentBag is a threaded linked list, which returns only the first node so try take will remove the first node and return it without compare. in c# out is used return not to send parametersStogner
See microsoft-docs out-paramJuju
@HopeMystery the ConcurrentBag<T> is not simply a thread-safe linked list. It is a collection of thread-local linked lists. It returns the first node of the current thread's linked list, if there is any, otherwise it steals an item from another thread's local linked list.Comatose
C
1

I am wondering about on which basis it removes object from the Bag,

The exact behavior of the ConcurrentBag<T>.TryTake method is not documented, so officially you are not allowed to make any assumption about which item will be removed. Any assumption that you make might be invalidated when the next version of the .NET runtime is released, since Microsoft reserves the right to change any undocumented behavior of any API without notice.

The current implementation of the ConcurrentBag<T> is based on something like a ThreadLocal<Queue<T>>. When you add an item in the collection, it is enqueued in the local queue (the queue of the current thread). When you take an item from the collection, you are given an item dequeued from the local queue, or, if the local queue is empty, an item stolen from another thread's local queue. Here is an experimental demonstration of this behavior:

ConcurrentBag<int> bag = new();
for (int i = 1; i <= 5; i++) bag.Add(i);
Thread t = new(_ => { for (int i = 6; i <= 10; i++) bag.Add(i); });
t.Start();
t.Join();
Console.WriteLine($"Contents: {String.Join(", ", bag)}");
Console.Write("Consumed: ");
while (bag.TryTake(out int item)) Console.Write($"{item}, ");

There are 2 threads involved, the current thread and the thread t. The current thread adds the items 1 - 5. The other thread adds the items 6 - 10. Then the current thread enumerates the collection, and finally it consumes it.

Output:

Contents: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Consumed: 5, 4, 3, 2, 1, 6, 7, 8, 9, 10, 

Notice the difference between enumerating the collection and consuming it. The enumeration resembles a stack, and the consumption resembles a queue that midway becomes a stack. The first item that is taken (5) is the last item added by the current thread. The last item that is taken (10) is the last item added by the other thread. The consuming order would be different if the other thread had done it. In that case it would be:

Consumed: 10, 9, 8, 7, 6, 1, 2, 3, 4, 5, 

Online demo.

At this point you might have realized that when the consumption order is important, the ConcurrentBag<T> is not a suitable collection. The main application for this collection is for creating object-pools. If you want to reduce memory allocations by storing reusable objects in a pool, the ConcurrentBag<T> might be the collection of choice. For practically any other use you are advised to stay away from this collection. Not only it's annoyingly unpredictable, but it is also equipped with a highly inefficient enumerator. Each time an enumeration starts, all the items in the collection are copied in a new array. So for example if the collection has 1,000,000 items and you call bag.First(), a new T[] array with 1,000,000 length will be created behind the scenes, just for returning a single item! A collection that is more suitable for use in general multithreading is the ConcurrentQueue<T>. The method ConcurrentQueue<T>.Enqueue enqueues the item at the end of the queue, so it is much closer conceptually to the method List<T>.Add than the method ConcurrentBag<T>.Add.

(I have posted more thoughts about the ConcurrentBag<T> in this answer.)

Comatose answered 1/2, 2023 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.