For a while at my company we've used a home-grown ObjectPool<T>
implementation that provides blocking access to its contents. It's pretty straightforward: a Queue<T>
, an object
to lock on, and an AutoResetEvent
to signal to a "borrowing" thread when an item is added.
The meat of the class is really these two methods:
public T Borrow() {
lock (_queueLock) {
if (_queue.Count > 0)
return _queue.Dequeue();
}
_objectAvailableEvent.WaitOne();
return Borrow();
}
public void Return(T obj) {
lock (_queueLock) {
_queue.Enqueue(obj);
}
_objectAvailableEvent.Set();
}
We have been using this and a few other collection classes instead of those provided by System.Collections.Concurrent
because we are using .NET 3.5, not 4.0. But recently we discovered that since we are using Reactive Extensions, we actually do have the Concurrent
namespace available to us (in System.Threading.dll).
Naturally, I figured that since BlockingCollection<T>
is one of the core classes in the Concurrent
namespace, it would probably offer better performance than anything I or my teammates wrote.
So I tried writing a new implementation that works very simply:
public T Borrow() {
return _blockingCollection.Take();
}
public void Return(T obj) {
_blockingCollection.Add(obj);
}
To my surprise, according to some simple tests (borrowing/returning to the pool a few thousand times from multiple threads), our original implementation significantly beats BlockingCollection<T>
in terms of performance. They both appear to work correctly; it's just that our original implementation seems to be much faster.
My question:
- Why would this be? Is it perhaps because
BlockingCollection<T>
offers greater flexibility (I understand it works by wrapping anIProducerConsumerCollection<T>
), which necessarily introduces performance overhead? - Is this just a flat-out misguided use of the
BlockingCollection<T>
class? - If this is an appropriate use of
BlockingCollection<T>
, am I just not using properly? For example, is theTake
/Add
approach overly simplistic, and there's a far better-performing way to get the same functionality?
Unless anyone has some insight to offer in response to that third question, it looks like we'll be sticking with our original implementation for now.