e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1122 lines
40 KiB
C#
1122 lines
40 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
|
//
|
|
// ConcurrentBag.cs
|
|
//
|
|
// <OWNER>[....]</OWNER>
|
|
//
|
|
//
|
|
//An unordered collection that allows duplicates and that provides add and get operations.
|
|
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Concurrent;
|
|
using System.Runtime.Serialization;
|
|
using System.Runtime.InteropServices;
|
|
using System.Diagnostics;
|
|
using System.Security.Permissions;
|
|
using System.Threading;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Diagnostics.Contracts;
|
|
|
|
namespace System.Collections.Concurrent
|
|
{
|
|
/// <summary>
|
|
/// Represents an thread-safe, unordered collection of objects.
|
|
/// </summary>
|
|
/// <typeparam name="T">Specifies the type of elements in the bag.</typeparam>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Bags are useful for storing objects when ordering doesn't matter, and unlike sets, bags support
|
|
/// duplicates. <see cref="ConcurrentBag{T}"/> is a thread-safe bag implementation, optimized for
|
|
/// scenarios where the same thread will be both producing and consuming data stored in the bag.
|
|
/// </para>
|
|
/// <para>
|
|
/// <see cref="ConcurrentBag{T}"/> accepts null reference (Nothing in Visual Basic) as a valid
|
|
/// value for reference types.
|
|
/// </para>
|
|
/// <para>
|
|
/// All public and protected members of <see cref="ConcurrentBag{T}"/> are thread-safe and may be used
|
|
/// concurrently from multiple threads.
|
|
/// </para>
|
|
/// </remarks>
|
|
#if !SILVERLIGHT
|
|
[Serializable]
|
|
#endif
|
|
[ComVisible(false)]
|
|
[DebuggerTypeProxy(typeof(SystemThreadingCollection_IProducerConsumerCollectionDebugView<>))]
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
#if !FEATURE_NETCORE
|
|
[HostProtection(Synchronization = true, ExternalThreading = true)]
|
|
#endif
|
|
public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>
|
|
{
|
|
|
|
// ThreadLocalList object that contains the data per thread
|
|
#if !SILVERLIGHT
|
|
[NonSerialized]
|
|
#endif
|
|
ThreadLocal<ThreadLocalList> m_locals;
|
|
|
|
// This head and tail pointers points to the first and last local lists, to allow enumeration on the thread locals objects
|
|
#if !SILVERLIGHT
|
|
[NonSerialized]
|
|
#endif
|
|
volatile ThreadLocalList m_headList, m_tailList;
|
|
|
|
// A flag used to tell the operations thread that it must synchronize the operation, this flag is set/unset within
|
|
// GlobalListsLock lock
|
|
#if !SILVERLIGHT
|
|
[NonSerialized]
|
|
#endif
|
|
bool m_needSync;
|
|
|
|
#if !SILVERLIGHT
|
|
// Used for custom serialization.
|
|
private T[] m_serializationArray;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ConcurrentBag{T}"/>
|
|
/// class.
|
|
/// </summary>
|
|
public ConcurrentBag()
|
|
{
|
|
Initialize(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ConcurrentBag{T}"/>
|
|
/// class that contains elements copied from the specified collection.
|
|
/// </summary>
|
|
/// <param name="collection">The collection whose elements are copied to the new <see
|
|
/// cref="ConcurrentBag{T}"/>.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
public ConcurrentBag(IEnumerable<T> collection)
|
|
{
|
|
if (collection == null)
|
|
{
|
|
throw new ArgumentNullException("collection", SR.GetString(SR.ConcurrentBag_Ctor_ArgumentNullException));
|
|
}
|
|
Initialize(collection);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Local helper function to initalize a new bag object
|
|
/// </summary>
|
|
/// <param name="collection">An enumeration containing items with which to initialize this bag.</param>
|
|
private void Initialize(IEnumerable<T> collection)
|
|
{
|
|
m_locals = new ThreadLocal<ThreadLocalList>();
|
|
|
|
// Copy the collection to the bag
|
|
if (collection != null)
|
|
{
|
|
ThreadLocalList list = GetThreadList(true);
|
|
foreach (T item in collection)
|
|
{
|
|
list.Add(item, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an object to the <see cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <param name="item">The object to be added to the
|
|
/// <see cref="ConcurrentBag{T}"/>. The value can be a null reference
|
|
/// (Nothing in Visual Basic) for reference types.</param>
|
|
public void Add(T item)
|
|
{
|
|
// Get the local list for that thread, create a new list if this thread doesn't exist
|
|
//(first time to call add)
|
|
ThreadLocalList list = GetThreadList(true);
|
|
AddInternal(list, item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <param name="list"></param>
|
|
/// <param name="item"></param>
|
|
private void AddInternal(ThreadLocalList list, T item)
|
|
{
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
#pragma warning disable 0420
|
|
Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add);
|
|
#pragma warning restore 0420
|
|
//Synchronization cases:
|
|
// if the list count is less than two to avoid conflict with any stealing thread
|
|
// if m_needSync is set, this means there is a thread that needs to freeze the bag
|
|
if (list.Count < 2 || m_needSync)
|
|
{
|
|
// reset it back to zero to avoid deadlock with stealing thread
|
|
list.m_currentOp = (int)ListOperation.None;
|
|
Monitor.Enter(list, ref lockTaken);
|
|
}
|
|
list.Add(item, lockTaken);
|
|
}
|
|
finally
|
|
{
|
|
list.m_currentOp = (int)ListOperation.None;
|
|
if (lockTaken)
|
|
{
|
|
Monitor.Exit(list);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to add an object to the <see cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <param name="item">The object to be added to the
|
|
/// <see cref="ConcurrentBag{T}"/>. The value can be a null reference
|
|
/// (Nothing in Visual Basic) for reference types.</param>
|
|
/// <returns>Always returns true</returns>
|
|
bool IProducerConsumerCollection<T>.TryAdd(T item)
|
|
{
|
|
Add(item);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to remove and return an object from the <see
|
|
/// cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <param name="result">When this method returns, <paramref name="result"/> contains the object
|
|
/// removed from the <see cref="ConcurrentBag{T}"/> or the default value
|
|
/// of <typeparamref name="T"/> if the operation failed.</param>
|
|
/// <returns>true if an object was removed successfully; otherwise, false.</returns>
|
|
public bool TryTake(out T result)
|
|
{
|
|
return TryTakeOrPeek(out result, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to return an object from the <see cref="ConcurrentBag{T}"/>
|
|
/// without removing it.
|
|
/// </summary>
|
|
/// <param name="result">When this method returns, <paramref name="result"/> contains an object from
|
|
/// the <see cref="ConcurrentBag{T}"/> or the default value of
|
|
/// <typeparamref name="T"/> if the operation failed.</param>
|
|
/// <returns>true if and object was returned successfully; otherwise, false.</returns>
|
|
public bool TryPeek(out T result)
|
|
{
|
|
return TryTakeOrPeek(out result, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper function to Take or Peek an item from the bag
|
|
/// </summary>
|
|
/// <param name="result">To receive the item retrieved from the bag</param>
|
|
/// <param name="take">True means Take operation, false means Peek operation</param>
|
|
/// <returns>True if succeeded, false otherwise</returns>
|
|
private bool TryTakeOrPeek(out T result, bool take)
|
|
{
|
|
|
|
// Get the local list for that thread, return null if the thread doesn't exit
|
|
//(this thread never add before)
|
|
ThreadLocalList list = GetThreadList(false);
|
|
if (list == null || list.Count == 0)
|
|
{
|
|
return Steal(out result, take);
|
|
}
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
if (take) // Take operation
|
|
{
|
|
#pragma warning disable 0420
|
|
Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Take);
|
|
#pragma warning restore 0420
|
|
//Synchronization cases:
|
|
// if the list count is less than or equal two to avoid conflict with any stealing thread
|
|
// if m_needSync is set, this means there is a thread that needs to freeze the bag
|
|
if (list.Count <= 2 || m_needSync)
|
|
{
|
|
// reset it back to zero to avoid deadlock with stealing thread
|
|
list.m_currentOp = (int)ListOperation.None;
|
|
Monitor.Enter(list, ref lockTaken);
|
|
|
|
// Double check the count and steal if it became empty
|
|
if (list.Count == 0)
|
|
{
|
|
// Release the lock before stealing
|
|
if (lockTaken)
|
|
{
|
|
try { }
|
|
finally
|
|
{
|
|
lockTaken = false; // reset lockTaken to avoid calling Monitor.Exit again in the finally block
|
|
Monitor.Exit(list);
|
|
}
|
|
}
|
|
return Steal(out result, true);
|
|
}
|
|
}
|
|
list.Remove(out result);
|
|
}
|
|
else
|
|
{
|
|
if (!list.Peek(out result))
|
|
{
|
|
return Steal(out result, false);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
list.m_currentOp = (int)ListOperation.None;
|
|
if (lockTaken)
|
|
{
|
|
Monitor.Exit(list);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Local helper function to retrieve a thread local list by a thread object
|
|
/// </summary>
|
|
/// <param name="forceCreate">Create a new list if the thread does ot exist</param>
|
|
/// <returns>The local list object</returns>
|
|
private ThreadLocalList GetThreadList(bool forceCreate)
|
|
{
|
|
ThreadLocalList list = m_locals.Value;
|
|
|
|
if (list != null)
|
|
{
|
|
return list;
|
|
}
|
|
else if (forceCreate)
|
|
{
|
|
// Acquire the lock to update the m_tailList pointer
|
|
lock (GlobalListsLock)
|
|
{
|
|
if (m_headList == null)
|
|
{
|
|
list = new ThreadLocalList(Thread.CurrentThread);
|
|
m_headList = list;
|
|
m_tailList = list;
|
|
}
|
|
else
|
|
{
|
|
|
|
list = GetUnownedList();
|
|
if (list == null)
|
|
{
|
|
list = new ThreadLocalList(Thread.CurrentThread);
|
|
m_tailList.m_nextList = list;
|
|
m_tailList = list;
|
|
}
|
|
}
|
|
m_locals.Value = list;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
Debug.Assert(list != null);
|
|
return list;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to reuse an unowned list if exist
|
|
/// unowned lists are the lists that their owner threads are aborted or terminated
|
|
/// this is workaround to avoid memory leaks.
|
|
/// </summary>
|
|
/// <returns>The list object, null if all lists are owned</returns>
|
|
private ThreadLocalList GetUnownedList()
|
|
{
|
|
//the global lock must be held at this point
|
|
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
|
|
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
if (currentList.m_ownerThread.ThreadState == System.Threading.ThreadState.Stopped)
|
|
{
|
|
currentList.m_ownerThread = Thread.CurrentThread; // the caller should acquire a lock to make this line thread safe
|
|
return currentList;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Local helper method to steal an item from any other non empty thread
|
|
/// It enumerate all other threads in two passes first pass acquire the lock with TryEnter if succeeded
|
|
/// it steals the item, otherwise it enumerate them again in 2nd pass and acquire the lock using Enter
|
|
/// </summary>
|
|
/// <param name="result">To receive the item retrieved from the bag</param>
|
|
/// <param name="take">Whether to remove or peek.</param>
|
|
/// <returns>True if succeeded, false otherwise.</returns>
|
|
private bool Steal(out T result, bool take)
|
|
{
|
|
#if !FEATURE_PAL && !SILVERLIGHT // PAL doesn't support eventing
|
|
if (take)
|
|
CDSCollectionETWBCLProvider.Log.ConcurrentBag_TryTakeSteals();
|
|
else
|
|
CDSCollectionETWBCLProvider.Log.ConcurrentBag_TryPeekSteals();
|
|
#endif
|
|
|
|
bool loop;
|
|
List<int> versionsList = new List<int>(); // save the lists version
|
|
do
|
|
{
|
|
versionsList.Clear(); //clear the list from the previous iteration
|
|
loop = false;
|
|
|
|
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
versionsList.Add(currentList.m_version);
|
|
if (currentList.m_head != null && TrySteal(currentList, out result, take))
|
|
{
|
|
return true;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
|
|
// verify versioning, if other items are added to this list since we last visit it, we should retry
|
|
currentList = m_headList;
|
|
foreach (int version in versionsList)
|
|
{
|
|
if (version != currentList.m_version) //oops state changed
|
|
{
|
|
loop = true;
|
|
if (currentList.m_head != null && TrySteal(currentList, out result, take))
|
|
return true;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
} while (loop);
|
|
|
|
|
|
result = default(T);
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// local helper function tries to steal an item from given local list
|
|
/// </summary>
|
|
private bool TrySteal(ThreadLocalList list, out T result, bool take)
|
|
{
|
|
lock (list)
|
|
{
|
|
if (CanSteal(list))
|
|
{
|
|
list.Steal(out result, take);
|
|
return true;
|
|
}
|
|
result = default(T);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// Local helper function to check the list if it became empty after acquiring the lock
|
|
/// and wait if there is unsynchronized Add/Take operation in the list to be done
|
|
/// </summary>
|
|
/// <param name="list">The list to steal</param>
|
|
/// <returns>True if can steal, false otherwise</returns>
|
|
private bool CanSteal(ThreadLocalList list)
|
|
{
|
|
if (list.Count <= 2 && list.m_currentOp != (int)ListOperation.None)
|
|
{
|
|
SpinWait spinner = new SpinWait();
|
|
while (list.m_currentOp != (int)ListOperation.None)
|
|
{
|
|
spinner.SpinOnce();
|
|
}
|
|
}
|
|
if (list.Count > 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the <see cref="ConcurrentBag{T}"/> elements to an existing
|
|
/// one-dimensional <see cref="T:System.Array">Array</see>, starting at the specified array
|
|
/// index.
|
|
/// </summary>
|
|
/// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
|
|
/// destination of the elements copied from the
|
|
/// <see cref="ConcurrentBag{T}"/>. The <see
|
|
/// cref="T:System.Array">Array</see> must have zero-based indexing.</param>
|
|
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying
|
|
/// begins.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
|
|
/// Visual Basic).</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
|
|
/// zero.</exception>
|
|
/// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the
|
|
/// length of the <paramref name="array"/>
|
|
/// -or- the number of elements in the source <see
|
|
/// cref="ConcurrentBag{T}"/> is greater than the available space from
|
|
/// <paramref name="index"/> to the end of the destination <paramref name="array"/>.</exception>
|
|
public void CopyTo(T[] array, int index)
|
|
{
|
|
if (array == null)
|
|
{
|
|
throw new ArgumentNullException("array", SR.GetString(SR.ConcurrentBag_CopyTo_ArgumentNullException));
|
|
}
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException
|
|
("index", SR.GetString(SR.ConcurrentBag_CopyTo_ArgumentOutOfRangeException));
|
|
}
|
|
|
|
// Short path if the bag is empty
|
|
if (m_headList == null)
|
|
return;
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
ToList().CopyTo(array, index);
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see
|
|
/// cref="T:System.Array"/>, starting at a particular
|
|
/// <see cref="T:System.Array"/> index.
|
|
/// </summary>
|
|
/// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
|
|
/// destination of the elements copied from the
|
|
/// <see cref="ConcurrentBag{T}"/>. The <see
|
|
/// cref="T:System.Array">Array</see> must have zero-based indexing.</param>
|
|
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying
|
|
/// begins.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
|
|
/// Visual Basic).</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
|
|
/// zero.</exception>
|
|
/// <exception cref="ArgumentException">
|
|
/// <paramref name="array"/> is multidimensional. -or-
|
|
/// <paramref name="array"/> does not have zero-based indexing. -or-
|
|
/// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/>
|
|
/// -or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is
|
|
/// greater than the available space from <paramref name="index"/> to the end of the destination
|
|
/// <paramref name="array"/>. -or- The type of the source <see
|
|
/// cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the
|
|
/// destination <paramref name="array"/>.
|
|
/// </exception>
|
|
void ICollection.CopyTo(Array array, int index)
|
|
{
|
|
if (array == null)
|
|
{
|
|
throw new ArgumentNullException("array", SR.GetString(SR.ConcurrentBag_CopyTo_ArgumentNullException));
|
|
}
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
((ICollection)ToList()).CopyTo(array, index);
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Copies the <see cref="ConcurrentBag{T}"/> elements to a new array.
|
|
/// </summary>
|
|
/// <returns>A new array containing a snapshot of elements copied from the <see
|
|
/// cref="ConcurrentBag{T}"/>.</returns>
|
|
public T[] ToArray()
|
|
{
|
|
// Short path if the bag is empty
|
|
if (m_headList == null)
|
|
return new T[0];
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
return ToList().ToArray();
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an enumerator that iterates through the <see
|
|
/// cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <returns>An enumerator for the contents of the <see
|
|
/// cref="ConcurrentBag{T}"/>.</returns>
|
|
/// <remarks>
|
|
/// The enumeration represents a moment-in-time snapshot of the contents
|
|
/// of the bag. 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 bag.
|
|
/// </remarks>
|
|
public IEnumerator<T> GetEnumerator()
|
|
{
|
|
// Short path if the bag is empty
|
|
if (m_headList == null)
|
|
return new List<T>().GetEnumerator(); // empty list
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
return ToList().GetEnumerator();
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an enumerator that iterates through the <see
|
|
/// cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <returns>An enumerator for the contents of the <see
|
|
/// cref="ConcurrentBag{T}"/>.</returns>
|
|
/// <remarks>
|
|
/// The items enumerated represent a moment-in-time snapshot of the contents
|
|
/// of the bag. It does not reflect any update to the collection after
|
|
/// <see cref="GetEnumerator"/> was called.
|
|
/// </remarks>
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return ((ConcurrentBag<T>)this).GetEnumerator();
|
|
}
|
|
#if !SILVERLIGHT
|
|
/// <summary>
|
|
/// Get the data array to be serialized
|
|
/// </summary>
|
|
[OnSerializing]
|
|
private void OnSerializing(StreamingContext context)
|
|
{
|
|
// save the data into the serialization array to be saved
|
|
m_serializationArray = ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct the stack from a previously seiralized one
|
|
/// </summary>
|
|
[OnDeserialized]
|
|
private void OnDeserialized(StreamingContext context)
|
|
{
|
|
m_locals = new ThreadLocal<ThreadLocalList>();
|
|
|
|
ThreadLocalList list = GetThreadList(true);
|
|
foreach (T item in m_serializationArray)
|
|
{
|
|
list.Add(item, false);
|
|
}
|
|
m_headList = list;
|
|
m_tailList = list;
|
|
|
|
m_serializationArray = null;
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Gets the number of elements contained in the <see cref="ConcurrentBag{T}"/>.
|
|
/// </summary>
|
|
/// <value>The number of elements contained in the <see cref="ConcurrentBag{T}"/>.</value>
|
|
/// <remarks>
|
|
/// The count returned represents a moment-in-time snapshot of the contents
|
|
/// of the bag. It does not reflect any updates to the collection after
|
|
/// <see cref="GetEnumerator"/> was called.
|
|
/// </remarks>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
// Short path if the bag is empty
|
|
if (m_headList == null)
|
|
return 0;
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
return GetCountInternal();
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value that indicates whether the <see cref="ConcurrentBag{T}"/> is empty.
|
|
/// </summary>
|
|
/// <value>true if the <see cref="ConcurrentBag{T}"/> is empty; otherwise, false.</value>
|
|
public bool IsEmpty
|
|
{
|
|
get
|
|
{
|
|
if (m_headList == null)
|
|
return true;
|
|
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
FreezeBag(ref lockTaken);
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
if (currentList.m_head != null)
|
|
//at least this list is not empty, we return false
|
|
{
|
|
return false;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
return true;
|
|
}
|
|
finally
|
|
{
|
|
UnfreezeBag(lockTaken);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is
|
|
/// synchronized with the SyncRoot.
|
|
/// </summary>
|
|
/// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized
|
|
/// with the SyncRoot; otherwise, false. For <see cref="ConcurrentBag{T}"/>, this property always
|
|
/// returns false.</value>
|
|
bool ICollection.IsSynchronized
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an object that can be used to synchronize access to the <see
|
|
/// cref="T:System.Collections.ICollection"/>. This property is not supported.
|
|
/// </summary>
|
|
/// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception>
|
|
object ICollection.SyncRoot
|
|
{
|
|
get
|
|
{
|
|
throw new NotSupportedException(SR.GetString(SR.ConcurrentCollection_SyncRoot_NotSupported));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A global lock object, used in two cases:
|
|
/// 1- To maintain the m_tailList pointer for each new list addition process ( first time a thread called Add )
|
|
/// 2- To freeze the bag in GetEnumerator, CopyTo, ToArray and Count members
|
|
/// </summary>
|
|
private object GlobalListsLock
|
|
{
|
|
get
|
|
{
|
|
Contract.Assert(m_locals != null);
|
|
return m_locals;
|
|
}
|
|
}
|
|
|
|
|
|
#region Freeze bag helper methods
|
|
/// <summary>
|
|
/// Local helper method to freeze all bag operations, it
|
|
/// 1- Acquire the global lock to prevent any other thread to freeze the bag, and also new new thread can be added
|
|
/// to the dictionary
|
|
/// 2- Then Acquire all local lists locks to prevent steal and synchronized operations
|
|
/// 3- Wait for all un-synchronized operations to be done
|
|
/// </summary>
|
|
/// <param name="lockTaken">Retrieve the lock taken result for the global lock, to be passed to Unfreeze method</param>
|
|
private void FreezeBag(ref bool lockTaken)
|
|
{
|
|
Contract.Assert(!Monitor.IsEntered(GlobalListsLock));
|
|
|
|
// global lock to be safe against multi threads calls count and corrupt m_needSync
|
|
Monitor.Enter(GlobalListsLock, ref lockTaken);
|
|
|
|
// This will force any future add/take operation to be synchronized
|
|
m_needSync = true;
|
|
|
|
//Acquire all local lists locks
|
|
AcquireAllLocks();
|
|
|
|
// Wait for all un-synchronized operation to be done
|
|
WaitAllOperations();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper method to unfreeze the bag from a frozen state
|
|
/// </summary>
|
|
/// <param name="lockTaken">The lock taken result from the Freeze method</param>
|
|
private void UnfreezeBag(bool lockTaken)
|
|
{
|
|
ReleaseAllLocks();
|
|
m_needSync = false;
|
|
if (lockTaken)
|
|
{
|
|
Monitor.Exit(GlobalListsLock);
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper method to release all local lists locks
|
|
/// </summary>
|
|
private void ReleaseAllLocks()
|
|
{
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
|
|
if (currentList.m_lockTaken)
|
|
{
|
|
currentList.m_lockTaken = false;
|
|
Monitor.Exit(currentList);
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper function to wait all unsynchronized operations
|
|
/// </summary>
|
|
private void WaitAllOperations()
|
|
{
|
|
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
|
|
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
if (currentList.m_currentOp != (int)ListOperation.None)
|
|
{
|
|
SpinWait spinner = new SpinWait();
|
|
while (currentList.m_currentOp != (int)ListOperation.None)
|
|
{
|
|
spinner.SpinOnce();
|
|
}
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper function to get the bag count, the caller should call it from Freeze/Unfreeze block
|
|
/// </summary>
|
|
/// <returns>The current bag count</returns>
|
|
private int GetCountInternal()
|
|
{
|
|
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
|
|
|
|
int count = 0;
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
checked
|
|
{
|
|
count += currentList.Count;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local helper function to return the bag item in a list, this is mainly used by CopyTo and ToArray
|
|
/// This is not thread safe, should be called in Freeze/UnFreeze bag block
|
|
/// </summary>
|
|
/// <returns>List the contains the bag items</returns>
|
|
private List<T> ToList()
|
|
{
|
|
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
|
|
|
|
List<T> list = new List<T>();
|
|
ThreadLocalList currentList = m_headList;
|
|
while (currentList != null)
|
|
{
|
|
Node currentNode = currentList.m_head;
|
|
while (currentNode != null)
|
|
{
|
|
list.Add(currentNode.m_value);
|
|
currentNode = currentNode.m_next;
|
|
}
|
|
currentList = currentList.m_nextList;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Inner Classes
|
|
|
|
/// <summary>
|
|
/// A class that represents a node in the lock thread list
|
|
/// </summary>
|
|
#if !SILVERLIGHT
|
|
[Serializable]
|
|
#endif
|
|
internal class Node
|
|
{
|
|
public Node(T value)
|
|
{
|
|
m_value = value;
|
|
}
|
|
public readonly T m_value;
|
|
public Node m_next;
|
|
public Node m_prev;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A class that represents the lock thread list
|
|
/// </summary>
|
|
internal class ThreadLocalList
|
|
{
|
|
// Tead node in the list, null means the list is empty
|
|
internal volatile Node m_head;
|
|
|
|
// Tail node for the list
|
|
private volatile Node m_tail;
|
|
|
|
// The current list operation
|
|
internal volatile int m_currentOp;
|
|
|
|
// The list count from the Add/Take prespective
|
|
private int m_count;
|
|
|
|
// The stealing count
|
|
internal int m_stealCount;
|
|
|
|
// Next list in the dictionary values
|
|
internal volatile ThreadLocalList m_nextList;
|
|
|
|
// Set if the locl lock is taken
|
|
internal bool m_lockTaken;
|
|
|
|
// The owner thread for this list
|
|
internal Thread m_ownerThread;
|
|
|
|
// the version of the list, incremented only when the list changed from empty to non empty state
|
|
internal volatile int m_version;
|
|
|
|
/// <summary>
|
|
/// ThreadLocalList constructor
|
|
/// </summary>
|
|
/// <param name="ownerThread">The owner thread for this list</param>
|
|
internal ThreadLocalList(Thread ownerThread)
|
|
{
|
|
m_ownerThread = ownerThread;
|
|
}
|
|
/// <summary>
|
|
/// Add new item to head of the list
|
|
/// </summary>
|
|
/// <param name="item">The item to add.</param>
|
|
/// <param name="updateCount">Whether to update the count.</param>
|
|
internal void Add(T item, bool updateCount)
|
|
{
|
|
checked
|
|
{
|
|
m_count++;
|
|
}
|
|
Node node = new Node(item);
|
|
if (m_head == null)
|
|
{
|
|
Debug.Assert(m_tail == null);
|
|
m_head = node;
|
|
m_tail = node;
|
|
m_version++; // changing from empty state to non empty state
|
|
}
|
|
else
|
|
{
|
|
node.m_next = m_head;
|
|
m_head.m_prev = node;
|
|
m_head = node;
|
|
}
|
|
if (updateCount) // update the count to avoid overflow if this add is synchronized
|
|
{
|
|
m_count = m_count - m_stealCount;
|
|
m_stealCount = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove an item from the head of the list
|
|
/// </summary>
|
|
/// <param name="result">The removed item</param>
|
|
internal void Remove(out T result)
|
|
{
|
|
Debug.Assert(m_head != null);
|
|
Node head = m_head;
|
|
m_head = m_head.m_next;
|
|
if (m_head != null)
|
|
{
|
|
m_head.m_prev = null;
|
|
}
|
|
else
|
|
{
|
|
m_tail = null;
|
|
}
|
|
m_count--;
|
|
result = head.m_value;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Peek an item from the head of the list
|
|
/// </summary>
|
|
/// <param name="result">the peeked item</param>
|
|
/// <returns>True if succeeded, false otherwise</returns>
|
|
internal bool Peek(out T result)
|
|
{
|
|
Node head = m_head;
|
|
if (head != null)
|
|
{
|
|
result = head.m_value;
|
|
return true;
|
|
}
|
|
result = default(T);
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Steal an item from the tail of the list
|
|
/// </summary>
|
|
/// <param name="result">the removed item</param>
|
|
/// <param name="remove">remove or peek flag</param>
|
|
internal void Steal(out T result, bool remove)
|
|
{
|
|
Node tail = m_tail;
|
|
Debug.Assert(tail != null);
|
|
if (remove) // Take operation
|
|
{
|
|
m_tail = m_tail.m_prev;
|
|
if (m_tail != null)
|
|
{
|
|
m_tail.m_next = null;
|
|
}
|
|
else
|
|
{
|
|
m_head = null;
|
|
}
|
|
// Increment the steal count
|
|
m_stealCount++;
|
|
}
|
|
result = tail.m_value;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the total list count, it's not thread safe, may provide incorrect count if it is called concurrently
|
|
/// </summary>
|
|
internal int Count
|
|
{
|
|
get
|
|
{
|
|
return m_count - m_stealCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// List operations
|
|
/// </summary>
|
|
internal enum ListOperation
|
|
{
|
|
None,
|
|
Add,
|
|
Take
|
|
};
|
|
#endregion
|
|
}
|
|
|
|
|
|
#region Internal Types
|
|
|
|
/// <summary>
|
|
/// A simple class for the debugger view window
|
|
/// </summary>
|
|
internal sealed class SystemThreadingCollection_IProducerConsumerCollectionDebugView<T>
|
|
{
|
|
IProducerConsumerCollection<T> m_collection;
|
|
public SystemThreadingCollection_IProducerConsumerCollectionDebugView(IProducerConsumerCollection<T> collection)
|
|
{
|
|
if (collection == null)
|
|
{
|
|
throw new ArgumentNullException("collection");
|
|
}
|
|
m_collection = collection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a snapshot of the underlying collection's elements.
|
|
/// </summary>
|
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
|
public T[] Items
|
|
{
|
|
get { return m_collection.ToArray(); }
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|