Reputation: 3990
I have a situation where multiple threads are creating one ICollection object. ConcurrentBag seems to be the best (?) solution, since - 1) each thread will have it's own local queue, and 2) the threads do not need to communicate - they are independent. So far so good, but the truth is I need to return an ISet from this method (after all producers had terminated). Even though the current ConcurrentBag instance IS distinct (it is guaranteed due to the logic of the application) , I still need to convert it to an ISet, say, HashSet. In this point there are no more producers. And now comes the real question:
When ConcurrentBag is being iterated, will the calling thread grab a lock for each item that is not in the thread's local queue? Or will it grab a lock once per thread? Also, is there a difference in the inner implementation between just iterating the bag - and between calling bag.Distinct() explicitly, lock-wise?
Upvotes: 2
Views: 718
Reputation: 39007
Having a look at the source code of the ConcurrentBag
: http://referencesource.microsoft.com/#System/sys/system/collections/concurrent/ConcurrentBag.cs,537a65e966c1c38d
Iterating through the bag triggers a call to FreezeBag
. This method calls AcquireAllLocks
, which browse the queue of each thread and set a lock:
/// <summary>
/// local helper method to acquire all local lists locks
/// </summary>
private void AcquireAllLocks()
{
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
bool lockTaken = false;
ThreadLocalList currentList = m_headList;
while (currentList != null)
{
// Try/Finally bllock to avoid thread aport between acquiring the lock and setting the taken flag
try
{
Monitor.Enter(currentList, ref lockTaken);
}
finally
{
if (lockTaken)
{
currentList.m_lockTaken = true;
lockTaken = false;
}
}
currentList = currentList.m_nextList;
}
}
It will grab a lock once per thread, not once per item.
Iterating or calling Distinct
both will call the GetEnumerator
method, so it doesn't make a difference.
Upvotes: 4