[Or: Rant - Code-Reviewing Random Code on the Internet.] I’m sure I’ve stumbled upon this post before once, and I hit it again today.
I think it’s a little unfortunate this appears so high up in the results, as the post has a bunch of problems. I don’t want to bash on it too hard, as it did one or two things that are nice. One is using IDisposable as a substitute for C# lock() statement when using ReaderWriteLockSlim. This is good. Both lock() and using(IDisposable) are prettier and easier ways to do an exception-safe try{}/finally{} pattern without writing boilerplate code (and bugs in it), which is something to be happy about. I guess it’s implemented like this:
public class WriteLock : IDisposable { private ReaderWriterLockSlim objLock;
public WriteLock(ReaderWriterLockSlim objLock) { this.objLock = objLock; this.objLock.EnterWriteLock(); }
public void Dispose() { this.objLock.ExitWriteLock(); } }
However, from here on things get a little shady.
The first thing that bugs me is the public surface of ThreadSafeQueue.
Each of these operations is ‘thread-safe’ in exactly one way – it prevents the internal state of the Queue object this.queue from becoming corrupted. However, none of these operations is actually helpful in writing code that is robust against race conditions. Consider a piece of code that tries to use .Peek():
Would this code work as intended? No! Well not unless you intended it to throw InvalidOperationException at random! Is there any redeeming situation where using .Count or .Peek() is useful, and any reason to include them in the class? Well yes – I can think of two so far: -you can use Count or Peek() to output information about your queue’s state for human consumption. Of course this information can become out of date at any time, but sometimes it’s nice just to have a rough idea, for instance as monitoring information. -it makes it a fraction more convenient when you want to poke at the queue in the debugger Immediate window, when you are debugging your race conditions…
Bizarrely, the class includes definition for both .Dequeue() and .TryDequeue() despite the fact that its implementation of .Dequeue() is clearly a best-effort operation which will throw upon failure. It’s bizarre because .TryDequeue() communicates that intent much more clearly, and so makes .Dequeue() redundant.
[Aside: There is a completely different behavior/semantic that could have been taken for dequeue which does not throw-upon failure, indeed it never fails – however the calling thread becomes blocked as long as no queue item exists. This would be an instance of the Monitor pattern for concurrency, with waiting and signalling.]
Last comment on ThreadSafeQueue, there is one method I suspect has an actual out-and-out bug in the class. If you read the whole class, did you spot it?
ReadLock is probably the correct construct to acquire an upgradable-read-lock in order to do a safe call to .Count. However, in order to call .Dequeue(), you also need to acquire WriteLock.
OK, so far we only covered the first class of the post: ThreadSafeQueue. What about ResourcePool<T>? Any bugs there?
Well, for a start, let’s look at .Get().
What about the counterpart of .Get() , .Release()?
In the case where Queue.Count > this.MaxResourceCount (which can happen since the actual function of MaxResourceCount is to limit the number of objects not in the queue, the object doesn’t actually get returned to the queue) the resource doesn’t get returned to the queue at all. And one guesses might just get leaked? Although it depends on the exact resource we are protecting whether it is ok to leak it or not, for some reason this worries me.
There are also some more interesting questions: when is it correct to Dispose() this class, which inherits IDisposable? And did this class need to inherit Disposable? Why?
Since it’s a resource shared between threads, the logical safe time to Dispose() it is when all threads have finished using the ResourcePool<T>. Let’s consider these points.
By this point I’ve mostly lost interest in understanding the rest of the code, but I do think I noticed one last inspired bug(?).
Never reuse threads? Hmm. So, just what was the point of having a ResourcePool<Thread> to act as a thread pool in the first place?… Never mind.
I’m feeling kind of guilty for ranting about someone else’s code like this. And also linking to it will probably just make it appear higher up in the search results, which is counterproductive. But please don’t hate me - concurrency bugs get me fired up so that I just couldn’t resist ranting!