Greetings, all. I have a philosophical/design question for you.
Consider a fairly typical scenario involving a producer/consumer pattern. Now, consider two possible ways of implementing such a pattern in a C-Sharp/.NET Windows Forms application. Presume, for the sake of the discussion framework, the GUI spins up a producer on one thread, and the consumer on the other. As a final constraint on the concept, presume that the Consumer will update the GUI with information from the Producer items. Let's assume further, for the sake of discussion, that proper accomodations will be made in the Consumer-GUI interaction to ensure context switches to the GUI thread when such updates take place.
Now, with that laid out, here are two possible design options regarding the production and consumption of items:
1. The producer could notify the creation of its items of interest via explicitly raised events (exposing appropriate EventHandler delegates), with the consumer subscribing to the events via an appropriate event handler.
2. The producer could, alternatively, add its items to a Queue object. The Queue would be shared between the producer and consumer, with the consumer polling on the queue until items are present to be consumed. Presume all checking on the queue would be wrapped by appropriate lock blocks for thread safety.
I consider pros and cons for each model. My instinct suggests that while the event-based model is probably "purer" from an OO perspective (primarily because there's absolutely no coupling to its caller), my trick knee tells me that the complexity of this solution will start to creep up, particularly if/when the need to implement event asynchronous event handlers arises. I also suspect that the complexity could creep up if it becomes necessary to raise discrete events for each type of item produced. Those combine to suggest to me that there's a risk of the consumer falling behind if the work of consumption starts to take more than a trivial amount of time.
Conversely, we have the "shared queue" model. Clearly, it has stronger coupling through the shared queue. But it does provide for what at least seems to me to be an opportunity for a fairly lightweight polling model - locking the shared queue only when checking for the presence of items, or to enqueue/dequeue items. This model also eliminates what I described earlier as possible "complexity creep" in the event-based model, because the need to raise explicit events as items are produced in this model is eliminated - the producer items are queued, and the consumer consumes them. The producer is also freed to continue producing nearly as rapidly or frequently as it may need, not being burdened with invoking event handlers and thus necessarily relieved of the issue of whether such handlers need to be asychronous. One could call this a "fire and forget" implementation.
With that baseline, what are some other opinions/thoughts on these two possible implementations that I'm possibly overlooking? Is there a clear reason why either should be avoided or preferred?
I realize some of the conclusions would depend upon implementation details I've not provided, hence the warning that the discussion is more philosophical or conceptual than literal at this point.
Thanks in advance.
SoonerDave
Consider a fairly typical scenario involving a producer/consumer pattern. Now, consider two possible ways of implementing such a pattern in a C-Sharp/.NET Windows Forms application. Presume, for the sake of the discussion framework, the GUI spins up a producer on one thread, and the consumer on the other. As a final constraint on the concept, presume that the Consumer will update the GUI with information from the Producer items. Let's assume further, for the sake of discussion, that proper accomodations will be made in the Consumer-GUI interaction to ensure context switches to the GUI thread when such updates take place.
Now, with that laid out, here are two possible design options regarding the production and consumption of items:
1. The producer could notify the creation of its items of interest via explicitly raised events (exposing appropriate EventHandler delegates), with the consumer subscribing to the events via an appropriate event handler.
2. The producer could, alternatively, add its items to a Queue object. The Queue would be shared between the producer and consumer, with the consumer polling on the queue until items are present to be consumed. Presume all checking on the queue would be wrapped by appropriate lock blocks for thread safety.
I consider pros and cons for each model. My instinct suggests that while the event-based model is probably "purer" from an OO perspective (primarily because there's absolutely no coupling to its caller), my trick knee tells me that the complexity of this solution will start to creep up, particularly if/when the need to implement event asynchronous event handlers arises. I also suspect that the complexity could creep up if it becomes necessary to raise discrete events for each type of item produced. Those combine to suggest to me that there's a risk of the consumer falling behind if the work of consumption starts to take more than a trivial amount of time.
Conversely, we have the "shared queue" model. Clearly, it has stronger coupling through the shared queue. But it does provide for what at least seems to me to be an opportunity for a fairly lightweight polling model - locking the shared queue only when checking for the presence of items, or to enqueue/dequeue items. This model also eliminates what I described earlier as possible "complexity creep" in the event-based model, because the need to raise explicit events as items are produced in this model is eliminated - the producer items are queued, and the consumer consumes them. The producer is also freed to continue producing nearly as rapidly or frequently as it may need, not being burdened with invoking event handlers and thus necessarily relieved of the issue of whether such handlers need to be asychronous. One could call this a "fire and forget" implementation.
With that baseline, what are some other opinions/thoughts on these two possible implementations that I'm possibly overlooking? Is there a clear reason why either should be avoided or preferred?
I realize some of the conclusions would depend upon implementation details I've not provided, hence the warning that the discussion is more philosophical or conceptual than literal at this point.
Thanks in advance.
SoonerDave
Comment