I'm not 100% on this, so I want an expert's input.
ConcurrentQueue<object> queue = new ConcurrentQueue<object>();
List<object> listA = queue.ToArray().ToList(); // A
List<object> listB = queue.ToList(); // B
I understand that ToArray() method will make a copy (as it is an internal method within ConcurrentQueue), but will calling the ToList() method directly do the same thing?
Simply, is it safe to refactor code from A to B?
If we look to the source code we wiil see that GetEnumerator is thread safe also, so I supose A and B are threadsafe both.
When you call .ToList() Linq call to constructor of List
public List(IEnumerable<T> collection) {
so the code are actually make a copy looks like a thread safe:
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Add(en.Current);
}
source code of ConcurrentQueue
source code of List
public IEnumerator<T> GetEnumerator()
{
// Increments the number of active snapshot takers. This increment must happen before the snapshot is
// taken. At the same time, Decrement must happen after the enumeration is over. Only in this way, can it
// eliminate race condition when Segment.TryRemove() checks whether m_numSnapshotTakers == 0.
Interlocked.Increment(ref m_numSnapshotTakers);
// Takes a snapshot of the queue.
// A design flaw here: if a Thread.Abort() happens, we cannot decrement m_numSnapshotTakers. But we cannot
// wrap the following with a try/finally block, otherwise the decrement will happen before the yield return
// statements in the GetEnumerator (head, tail, headLow, tailHigh) method.
Segment head, tail;
int headLow, tailHigh;
GetHeadTailPositions(out head, out tail, out headLow, out tailHigh);
//If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of
// the queue is not taken when GetEnumerator is initialized but when MoveNext() is first called.
// This is inconsistent with existing generic collections. In order to prevent it, we capture the
// value of m_head in a buffer and call out to a helper method.
//The old way of doing this was to return the ToList().GetEnumerator(), but ToList() was an
// unnecessary perfomance hit.
return GetEnumerator(head, tail, headLow, tailHigh);
}
remarks of the enumerator also says that we can use it concurently:
/// The enumeration represents a moment-in-time snapshot of the contents
/// of the queue. It does not reflect any updates to the collection after
/// <see cref="GetEnumerator"/> was called. The enumerator is safe to use
/// concurrently with reads from and writes to the queue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With