Yeast
Throughout my blog, you'll see references to code under the Yeast.* namespace. Yeast is the name of a framework that I've created for my personal use over the years whose purpose is to solve problems for which i could not find a solution in the .NET base class libraries. As I blog, I'll make reference to content in the Yeast framework so as to clarify the code upon which my blog posts are based. Sometimes, I'll include just the class metadata if the blog post isn't really about the Yeast class but just uses it. In other cases, I'll include full source code if it is my intention to explain the Yeast class's capabilities themselves.
Now, the common question: "Why the heck is the framework called Yeast?" It's a metaphor for adding yeast to bread dough so that it'll rise. So, by adding Yeast to your software, you'll make it rise too. Nobody wants a flat app, now do they. Also, I do my very best not to compete with pre-existing or new solutions to problems and welcome having my Yeast code become obsolete because Microsoft has introduced a solution. As an example, the original version of this post introduced a class I called BlockingQueue<T> that implemented a thread safe producer-consumer queue. Thanks to that original post version Stephen Toub turned me on to BlockingCollection<T> which gives me exactly what I need. I'm ripping out BlockingQueue<T> from Yeast as we speak. For perpetuity sake, this is what BlockingQueue looks like.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace Yeast.Collections.Generic
{
// Summary:
// Implements a queue which blocks all consumer threads if the queue is empty,
// and signals any waiting consumers once data becomes available.
public class BlockingQueue<T> : IEnumerable<T>, IEnumerable
{
// Summary:
// THe object used to ensure that the methods herein are thread safe.
public readonly object SyncRoot;
public BlockingQueue();
//
//
// Parameters:
// capacity:
// The maximum capacity of the underlying queue.
//
// Exceptions:
// System.ArgumentException:
// If the capacity specified is less than one, this constructor will fail.
public BlockingQueue(int capacity);
// Summary:
// Gets the capacity of this instance as specified in the constructor.
public int Capacity { get; }
//
// Summary:
// Gets the number of elements currently in the queue.
public int Count { get; }
//
// Summary:
// Set when the queue has no elements in it.
public WaitHandle Empty { get; }
//
// Summary:
// Set when the queue has reached it's Capacity.
public WaitHandle Full { get; }
// Summary:
// Removes an element from the queue and returns it. This method will block
// if the queue is empty and will unblock once an item has been enqueued.
//
// Returns:
// An item of type T that is at the front of the queue.
public T Dequeue();
//
// Summary:
// Adds an element to the back of the queue.
//
// Parameters:
// element:
// The element to be added to the queue.
//
// Remarks:
// If the queue is empty prior to this operation, the Empty event will be set.
// If the queue reaches its capacity as a result of this operation, the Full
// event will be set.
public void Enqueue(T element);
//
// Summary:
// Implements an enumerator that internally calls Yeast.Collections.Generic.BlockingQueue<T>.Dequeue()
// so as to yield the next elements in the queue. The resulting enumerator
// will behave just as if Dequeue were called in a loop.
//
// Returns:
// An enumerator that internally dequeues elements from the queue infinitely.
public IEnumerator<T> GetEnumerator();
}
}
Comments
Anonymous
January 21, 2012
Hi Sean- re: "since it doesn't yet exist in the .NET Framework" You might be interested in the System.Collections.Concurrent.BlockingCollection<T> type that was added to the Framework in .NET 4.Anonymous
January 21, 2012
Ah perfect. This is exactly what I needed and couldn't find. Yes, I did write this during the .NET 3.5SP1 days.Anonymous
January 22, 2012
BlockingCollection<T> does implement FIFO semantics by default because it wraps a ConcurrentQueue<T>. Note that BlockingCollection<T> is a blocking wrapper around an IProducerConsumerCollection<T>, so you can wrap it around a ConcurrentQueue<T> (for FIFO, the default), or ConcurrentStack<T> (for LIFO), or ConcurrentBag<T> (no particular ordering), or any other such implementation you'd like to provide. What makes you believe BlockingCollection<T> is LIFO? Is there a mistake in the docs somewhere that should be fixed?Anonymous
January 22, 2012
The Add and Take methods on BlockingCollection<T> can't specify where they add or remove from, because it's entirely up to the underlying impelemtation of IProducerConsumerCollection; that's also why the names are Add and Take rather than Enqueue and Dequeue, since "Enqueue" and "Dequeue" are typically reserved for FIFO semantics, whereas Push and Pop are typically reserved for LIFO, and BlockingCollection<T> could be either or neither based on the underlying collection it's wrapping. Similarly, IProducerConsumerCollection's TryAdd and TryTake can't specify where they add or remove from, because it's entirely up to the implementation. In contrast, the documentation for ConcurrentQueue<T> and ConcurrentStack<T> do document that they're FIFO and LIFO, respectively, and BlockingCollection<T>'s parameterless constructor's documentation specifies that since no IProducerConsumerCollection<T> was explicitly specified, it defaults to using a ConcurrentQueue<T> (see msdn.microsoft.com/.../dd287116.aspx). I hope that helps.Anonymous
January 22, 2012
Yeah, thanks. Perfect. I should have just unit tested it myself to begin with to prove it to myself. Anyway, how glad I am that this blog post's point wasn't my little BlockingQueue.