Circular Buffers are use for data transfer between two processes. The Producer process places items into the Circular Buffer and the Consumer process removes them. The variable capacity of the Circular Buffer accommodates timing differences between the Producer and Consumer processes.

The Circular Buffer can execute faster than than other queues that hold a variable amount of data since a fixed size block of memory is allocated just once from memory management and then reused (the Circular Buffer can be visualized as such but is actually a linear buffer with indices that wrap, modulo the buffer size, when the end of the buffer is reached).

Often, the Circular Buffer is used to decouple two processes that operate at different speeds. For example, a faster Producer process can "burst" data into the buffer and continue with its processing. A slower Consumer of that data can then read it at its own rate without synchronizing and slowing the Producer. In this type of application, the average rate, over time, of both processes must be the same to avoid an over or under flow condition of the Circular Buffer (this is the "Synchronous Mode" of operation). Also, sequencing is critical. Unless one of the synchronizing methods described below is used, the Producer process must always execute first.

In other types of applications, overflow of the Circular Buffer and attendant data loss is acceptable. For example--Error Logging. Here process state data is continuously written into the Circular Buffer at a high rate but only the last few logged items are useful in troubleshooting a problem that might take hours or days to repeat. A buffer of size N is saturated with the last N-1 items written by the Producer and read when the error condition is detected. This is the "Asynchronous Mode" of the Circular Buffer.

In the Synchronous Mode, two methods are available to ensure that no data is lost. The Blocking method and the WaterMark method.

Calling Blocking methods will cause a calling thread to block until the Circular Buffer has the requested number of items. Note that blocking pairs must be used. That is, if the DequeueBlocking or CopyToBlocking methods are used, the EnqueueBlocking method must also be used. The WaterMark method will fire an event when the number of items in the Circular buffer reaches a preset level - eliminating wasted processing time due to process blocks. Example usage:

The Winform application I provide isn't terribly useful for architecting a system using the Circular Buffer. I used it to test the class and then added the GUI for fun. But it might be a useful learning device if you are unfamiliar with the concepts. It simulates a N=36 size Circular Buffer with Producer and Consumer threads. It operates a little like a clock doesn't it.

The IEnumerator interface is supported and the items can be read from the Circular Buffer with foreach().

The DLL is signed for installation in the GAC.

Using the Circular Buffer in multi-threaded applications is very complex and subject to possible timing hazards. This is version 1.0.0.0 so beware!

Watch out for the blocking methods, at the last minute I decided to use an AutoResetEvent for synchronization because I wanted to learn how to use one. But to the degree tested, everything seems to work. Please report bugs (and fixes?) to me.

Thanks to the .NET team for giving us such amazing technology and for making this stuff so much fun.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Hi,I created a tester for your class:I used sync mode and blocking methods.The producers does not stop and wait when it full, the consumer too, does 2 rounds of consuming and then stop(it sould stop immediately).I think this project is very useful and I want to help u improve it.

I tried but, cannot reproduce the bug on your gui. I simply open two thread - consumer and producer. Init new queue with 10 in the constructor.producer get random number and call EnqueueBlocking(randNum) - loop this every 1 sec.that's all the test.My results are:producer keep working (does not stop - count start from 0 every 9 elements)

then remove producer and start the consumer that call DequeueBlocking - loop this every 1 sec. it keep looping twice on the buffer (insted of stop when no data in)

Oren,Thanks.Please feel free to improve/extend the code. I have been meaning to do an inter-process demo but haven't found the time.I extended the .NET debugging trace (I think it is called) to use the circular buffer but haven't written that up yet.Good luck but watch out for timing hazards.Bob H.

Can this code be used to exchange data between two processes? It looks from the description of your article that it is possible. But from code, it looks like it's meant to be used in same process space.

I think this was a cut and paste error.What I wanted to do was allow some ctors with specific types of objects to be created (like an array of ints)and then to compare the speed of operations with the generic object. But I forgot to change from the generic type and I never got aroung to doing the check.Thanks for catching that.Bob H.

You are right on passing ValueType such as Byte[] or int[] instead of generic object will boost the speed. However, if implemented IEnumerator method Current being used, which boxing each ValueType to object, and caller need to un-boxing each Object to ValueType, then we will get big performance hit here. So, is there any good way to avoid it or .NET is not a good solution for fast-speed-required application, such as realtime data aquisition application? What's your opinion?

And also correct that I wanted to characterize .NET for some real-time processing--and the Circular Buffer is the place to start. Some kind of hybrid implementation using C++ and PInvoke will probably be needed.

I'll get back to the project one day and will update the code when it is improved. You are welcome to send-in or submit to CP your own code.Bob H.