Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Thread Safe Generic Queue Class

, 2 Oct 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
I've been doing a lot of mult-threading work, recently, using the standard Thead 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'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.

/// <span class="code-SummaryComment"><summary> 
</span>/// The private q which holds the actual data 
/// <span class="code-SummaryComment"></summary> 
</span>private readonly Queue<T> m_Queue; 

/// <span class="code-SummaryComment"><summary> 
</span>/// Lock for the Q 
/// <span class="code-SummaryComment"></summary> 
</span>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.

/// <span class="code-SummaryComment"><summary> 
</span>/// Initializes the Queue 
/// <span class="code-SummaryComment"></summary> 
</span>public TQueue() 
{ 
    m_Queue = new Queue<T>(); 
} 
  
/// <span class="code-SummaryComment"><summary> 
</span>/// Initializes the Queue 
/// <span class="code-SummaryComment"></summary> 
</span>/// <span class="code-SummaryComment"><param name="capacity">the initial number of elements the queue can contain</param> 
</span>public TQueue(int capacity) 
{ 
    m_Queue = new Queue<T>(capacity); 
} 

/// <span class="code-SummaryComment"><summary> 
</span>/// Initializes the Queue 
/// <span class="code-SummaryComment"></summary> 
</span>/// <span class="code-SummaryComment"><param name="collection">the collection whose 
</span>/// members are copied to the Queue<span class="code-SummaryComment"></param> 
</span>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.

/// <span class="code-SummaryComment"><summary> 
</span>/// Returns an enumerator that enumerates through the collection 
/// <span class="code-SummaryComment"></summary> 
</span>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.

/// <span class="code-SummaryComment"><summary> 
</span>/// Adds an item to the queue 
/// <span class="code-SummaryComment"></summary> 
</span>/// <span class="code-SummaryComment"><param name="item">the item to add to the queue</param> 
</span>public void Enqueue(T item) 
{ 
    LockQ.EnterWriteLock(); 
    try 
    { 
        m_Queue.Enqueue(item); 
    } 
  
    finally 
    { 
        LockQ.ExitWriteLock(); 
    } 
} 
/// <span class="code-SummaryComment"><summary> 
</span>/// Removes and returns the item in the beginning of the queue 
/// <span class="code-SummaryComment"></summary> 
</span>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).

/// <span class="code-SummaryComment"><summary> 
</span>/// Enqueues the list of items 
/// <span class="code-SummaryComment"></summary> 
</span>/// <span class="code-SummaryComment"><param name="ItemsToQueue">list of items to enqueue</param> 
</span>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(); 
    } 
} 

/// <span class="code-SummaryComment"><summary> 
</span>/// Enqueues the list of items 
/// <span class="code-SummaryComment"></summary> 
</span>/// <span class="code-SummaryComment"><param name="ItemsToQueue">list of items to enqueue</param> 
</span>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.

/// <span class="code-SummaryComment"><summary> 
</span>/// Dequeues all the items and returns them as a thread safe list 
/// <span class="code-SummaryComment"></summary> 
</span>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 >

SocialTwist Tell-a-Friend

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.


License

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

Share

About the Author

merlin981
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

 
QuestionWhy not use lock{}, its faster? Pinmemberkauseeman3-Dec-10 11:56 
AnswerRe: Why not use lock{}, its faster? Pinmembermerlin9814-Dec-10 2:43 
GeneralRe: Why not use lock{}, its faster? Pinmemberkauseeman4-Dec-10 4:03 
GeneralRe: Why not use lock{}, its faster? Pinmembermerlin9815-Dec-10 2:13 
GeneralA small Bug Pinmemberphilippe dykmans6-Sep-10 6:39 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 2 Oct 2009
Article Copyright 2009 by merlin981
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid