Click here to Skip to main content
15,891,473 members
Articles / Programming Languages / C#

Thread Safe Generic Queue Class

Rate me:
Please Sign up or sign in to vote.
4.86/5 (9 votes)
11 Aug 2009CPOL2 min read 53.5K   31   9
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.

C#
/// <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.

C#
/// <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.

C#
/// <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.

C#
/// <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();
    }
}
C#
/// <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 led to the creation of the EnqueueAll functions. You'll notice the second overload is using the thread safe List (TList).

C#
/// <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.

C#
/// <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();
    }
}


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Winner - Best Mobile App - AT&T Developer Summit, Las Vegas, 2013

My personal resume can be found at: http://www.philippiercedeveloper.com

My game portfolio can be found at: http://www.rocketgamesmobile.com

About Philip Pierce:

I am a software developer with twenty years experience in game development, mobile, web, desktop, server, and database. My extensive background highlights an expertise in rapid application development using the latest Microsoft, Mobile, and Game Development technologies, along with the ability to create AI for games and business software, redesign existing software, develop multi-threaded software, and create client/server applications.

Comments and Discussions

 
QuestionConcurrentQueue<T> Pin
Member 876402327-Sep-12 23:29
Member 876402327-Sep-12 23:29 
GeneralMy vote of 5 Pin
aka MasteR27-Jun-12 23:44
aka MasteR27-Jun-12 23:44 
GeneralI have found that. Pin
reborn_zhang12-Feb-10 23:00
reborn_zhang12-Feb-10 23:00 
GeneralImplementing your thread-safe Queue in a VB project Pin
bobishkindaguy9-Feb-10 8:20
bobishkindaguy9-Feb-10 8:20 
Currently my projects are in VB, and I have converted your C# project to VB.

Since VB has no yield command, I have been trying to implement your idea by incorporating a substitute class explained by Matthew Doig, here:
"blog.matthewdoig.com/?p=81"

He suggests using a "superclass" iterator that can emulate the yield command. I have added that class to my VB project.

However, the example he uses to illustrate the usage of the superclass is a "workflow/monad/Linq expression", which my extremely small brain has not been able to grasp. The statement is generating a Fibonacci sequence, which in C# is like this:
IEnumerable<int> Fibs()
{
    int n1 = 0;
    int n2 = 1;

    while (true)
    {
        int n3 = n1 + n2;
        yield return n3;
        n1 = n2;
        n2 = n3;
    }
}


His article shows how to accomplish the above using VB, and I have pasted his code below.

If you have a moment, could you show how one might use his example with your Public Function GetEnumerator() As IEnumerator(Of T)could be altered to use Matthew's "superclass" in place of yield ?

I simply created a new WindowsApplication1 and put the following code into the Form1 code window. It uses his "workflow/monad/Linq expression".

Public Class Form1
  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    'Matthew's "workflow/monad/Linq expression"
    Dim result As Integer = New Fibs().TakeWhile(Function(x) x < 4000000).Where(Function(x) x Mod 2 = 0).Sum()
    MessageBox.Show(result.ToString)
    End
  End Sub
End Class

Public Class Fibs
  Inherits Generator(Of Integer)
  Private a As Integer = 0
  Private b As Integer = 1

  Protected Overrides Function Generate() As Integer
    b = a + b
    a = b - a
    Return b
  End Function
End Class

Public MustInherit Class Generator(Of T)
  Implements IEnumerable(Of T)
  Implements IEnumerator(Of T)

  Private mCurrent As T

  Public Sub New()
  End Sub

  Protected MustOverride Function Generate() As T

  Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of T) Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator
    Return Me
  End Function
  Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Return Me
  End Function
  Public ReadOnly Property Current() As T Implements System.Collections.Generic.IEnumerator(Of T).Current
    Get
      Return mCurrent
    End Get
  End Property
  Public ReadOnly Property Current1() As Object Implements System.Collections.IEnumerator.Current
    Get
      Return mCurrent
    End Get
  End Property
  Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
    mCurrent = Generate()
    Return True
  End Function
  Public Sub Reset() Implements System.Collections.IEnumerator.Reset
    Throw New NotImplementedException()
  End Sub

  Private mDisposedValue As Boolean = False        ' To detect redundant calls

  ' IDisposable
  Protected Overridable Sub Dispose(ByVal Disposing As Boolean)
    If Not Me.mDisposedValue Then
      If Disposing Then
        ' TODO: free other state (managed objects).
      End If

      ' TODO: free your own state (unmanaged objects).
      ' TODO: set large fields to null.
    End If
    Me.mDisposedValue = True
  End Sub

#Region " IDisposable Support "
  ' This code added by Visual Basic to correctly implement the disposable pattern.
  Public Sub Dispose() Implements IDisposable.Dispose
    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
    Dispose(True)
    GC.SuppressFinalize(Me)
  End Sub
#End Region

End Class
____________________________________________________________________________________
The Vulcan Science Directorate has determined that time travel is impossible.

GeneralGetEnumerator not thread-safe Pin
Gideon Engelberth30-Sep-09 9:40
Gideon Engelberth30-Sep-09 9:40 
GeneralRe: GetEnumerator not thread-safe Pin
supercat95-Oct-09 11:07
supercat95-Oct-09 11:07 
Generaloverload Pin
jpmik12-Aug-09 14:26
jpmik12-Aug-09 14:26 
GeneralLike it Pin
User 167325211-Aug-09 12:48
User 167325211-Aug-09 12:48 
GeneralRe: Like it Pin
merlin98111-Aug-09 15:12
professionalmerlin98111-Aug-09 15:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.