Thread Safe Generic Queue Class
Thread safe generic queue class
I've been doing a lot of multi-threading work recently using the standard Thread
class, the Worker Queue, and the new PLINQ (Parallel LINQ). The problem with most of the built-in generic collections (Queue<>
, List<>
, Dictionary<>
, etc.), is that they are not thread safe.
I created a library of thread safe collections which allow me to use the standard generic collection actions (foreach
, LINQ, etc.), while at the same time being thread safe.
The classes in this library inherit from the appropriate collection interface
(IEnumerable
, ICollection
, etc.). Each class also has all the functions and properties that its original non-thread safe class has.
You can download a copy of the entire library, which includes support for a thread safe List<>
, Dictionary<>
, and Queue<>
, here: Thread Safe Generic Collections.
TQueue<> Example
The first thing we need to do is create a container for the TQueue
and a thread lock object. I generally prefer to use the ReaderWriterLockSlim
because it is light weight and fast.
/// <summary>
/// The private q which holds the actual data
/// </summary>
private readonly Queue<T> m_Queue;
/// <summary>
/// Lock for the Q
/// </summary>
private readonly ReaderWriterLockSlim LockQ = new ReaderWriterLockSlim();
Just like a standard Queue
, we have three overloads for the Initialization. These overloads allow an empty Queue
to be created, a Queue
with a specified capacity, or a Queue
with an initial IEnumerable
collection to populate the Queue
.
/// <summary>
/// Initializes the Queue
/// </summary>
public TQueue()
{
m_Queue = new Queue<T>();
}
/// <summary>
/// Initializes the Queue
/// </summary>
/// <param name="capacity">the initial number of elements the queue can contain</param>
public TQueue(int capacity)
{
m_Queue = new Queue<T>(capacity);
}
/// <summary>
/// Initializes the Queue
/// </summary>
/// <param name="collection">the collection whose
/// members are copied to the Queue</param>
public TQueue(IEnumerable<T> collection)
{
m_Queue = new Queue<T>(collection);
}
This next function is probably the most important one. The GetEnumerator()
is used during ForEach
loops, and returns the next item in the collection. Following Microsoft's example of a thread-safe enumerator, we first get a copy of the current container Queue
, then use this copy for iterating. You'll notice the use of the Read
lock before acquiring the container Queue
copy.
/// <summary>
/// Returns an enumerator that enumerates through the collection
/// </summary>
public IEnumerator<T> GetEnumerator()
{
Queue<T> localQ;
// init enumerator
LockQ.EnterReadLock();
try
{
// create a copy of m_TList
localQ = new Queue<T>(m_Queue);
}
finally
{
LockQ.ExitReadLock();
}
// get the enumerator
foreach (T item in localQ)
yield return item;
}
A Queue
must include an Enqueue
and a Dequeue
, used for adding and removing items from the collection. Just as in every other function, we're using the locks to protect our data access.
/// <summary>
/// Adds an item to the queue
/// </summary>
/// <param name="item">the item to add to the queue</param>
public void Enqueue(T item)
{
LockQ.EnterWriteLock();
try
{
m_Queue.Enqueue(item);
}
finally
{
LockQ.ExitWriteLock();
}
}
/// <summary>
/// Removes and returns the item in the beginning of the queue
/// </summary>
public T Dequeue()
{
LockQ.EnterWriteLock();
try
{
return m_Queue.Dequeue();
}
finally
{
LockQ.ExitWriteLock();
}
}
I found that many times, I have a need to enqueue multiple items at once. This leads to the creation of the EnqueueAll
functions. You'll notice the second overload is using the thread safe List (TList
).
/// <summary>
/// Enqueues the list of items
/// </summary>
/// <param name="ItemsToQueue">list of items to enqueue</param>
public void EnqueueAll(IEnumerable<T> ItemsToQueue)
{
LockQ.EnterWriteLock();
try
{
// loop through and add each item
foreach (T item in ItemsToQueue)
m_Queue.Enqueue(item);
}
finally
{
LockQ.ExitWriteLock();
}
}
/// <summary>
/// Enqueues the list of items
/// </summary>
/// <param name="ItemsToQueue">list of items to enqueue</param>
public void EnqueueAll(TList<T> ItemsToQueue)
{
LockQ.EnterWriteLock();
try
{
// loop through and add each item
foreach (T item in ItemsToQueue)
m_Queue.Enqueue(item);
}
finally
{
LockQ.ExitWriteLock();
}
}
And, since we have an EnqueueAll
, I also found a need to dequeue everything at once. DequeueAll
returns a thread safe list (TList
), instead of the standard List
.
/// <summary>
/// Dequeues all the items and returns them as a thread safe list
/// </summary>
public TList<T> DequeueAll()
{
LockQ.EnterWriteLock();
try
{
// create return object
TList<T> returnList = new TList<T>();
// dequeue until everything is out
while (m_Queue.Count > 0)
returnList.Add(m_Queue.Dequeue());
// return the list
return returnList;
}
finally
{
LockQ.ExitWriteLock();
}
}
No related posts.
Related posts brought to you by Yet Another Related Posts plug in.