System.Collections.Concurrent has some new collections that work very well in multithreaded environments. However, they are a bit limited. Either they block until an item becomes available, or they return default(T) (TryXXX methods).
I’m needing a collection that is thread safe, but instead of blocking the calling thread it uses a callback to inform me that at least one item is available.
My current solution is to use a BlockingCollection, but to use the APM with a delegate to get the next element. In other words, I create a delegate to a method that Takes from the collection, and execute that delegate using BeginInvoke.
Unfortunately, I have to keep a lot of state within my class in order to accomplish this. Worse, the class is not thread safe; it can only be used by a single thread. I’m skirting the edge of maintainability, which I’d prefer not to do.
I know there are some libraries out there that make what I’m doing here pretty simple (I believe the Reactive Framework is one of these), but I’d like to accomplish my goals without adding any references outside of version 4 of the framework.
Are there any better patterns I can use that don’t require outside references that accomplish my goal?
tl;dr:
Are there any patterns that satisfy the requirement:
“I need to signal a collection that I am ready for the next element, and have the collection execute a callback when that next element has arrived, without any threads being blocked.”
I think I have two possible solutions. I am not particularly satisfied with either, but they do at least provide a reasonable alternative to the APM approach.
The first does not meet your requirement of no blocking thread, but I think it is rather elegant because you can register callbacks and they will get called in round-robin fashion, but you still have the ability to call
TakeorTryTakeas you normally would for aBlockingCollection. This code forces callbacks to be registered each time an item is requested. That is the signalling mechanism for the collection. The nice thing about this approach is that calls toTakedo not get starved as they do in my second solution.The second does meet your requirement of no blocking thread. Notice how it transfers the invocation of the callback to the thread pool. I did this because I am thinking that if it got executed synchronously then the locks would be held longer resulting in the bottlenecking of
AddandRegisterForTake. I have looked it over closely and I do not think it can get live locked (both an item and a callback are available, but the callback never gets executed) but you might want to look it over yourself to verify. The only problem here is that a call toTakewould get starved as callbacks always take priority.