Suppose I have a WPF application where I have the following architecture:
[Worker Thread] -> [Queue 1] -> [Queue Manager Thread] -> [Queue 2] -> [UI Thread]
The Worker is listening to data from some service and receives that data at indeterminate times (could be multiple times per second or a few times every few seconds) then queues it to Queue 1, then the Queue Manager, depending on the “health” of the UI thread may decide to throttle up / down the rate that data items are queued to Queue 2, which the UI thread uses to update the UI, maybe dropping a few items, so as not to overwhelm the UI thread in the event it is receiving too many messages (eg it might decide that it will check the timestamp of each data message and only enqueue it to Queue 2 if the difference between messages is at least 5 seconds older than the last data item the UI updated itself with)
The UI thread will have a timer that fires a set-interval to update the UI with new data from Queue 2. What I’d like to be able to do is determine how fast the UI is updating itself to measure it’s “responsiveness” in order to throttle eg increase / decrease the timer-interval as to how often to update the UI
Suppose my UI had lots of controls (grids, charts etc) all bound to different filtered / grouped subsets of the data on Queue 2 in my UI shell and the UI starts becoming unresponsive and freeze between updates when updating those controls, how could I detect this from code in order to know how / when to increase / decrease the interval for UI updates? Basically, how do I measure how long it takes to rebind the entire UI across all controls bound to the data?
BTW is this a good design or could it be improved? Are there any other strategies I could consider?
I would simply drop the QueueManager and Queue2.
All you need is a WPF Dispatcher Timer (and not a Timers.Timer) that checks for input every few ms. You can statically configure that timeout.
And then make Queue1 a blocking queue to throttle whatever is upstream.
Edit
The Dispatcher Timer can be started with a certain priority. Pick a low one, like Background or ContextIdle and you will never overload the GUI.
Then just make sure the Timer doesn’t bite off more than it can chew. Only 1 (or a few) items at a time. Tune this part so that the GUI will process as much as it can but no more. The runtime adjustment comes for free because you’re tied into the dispatcher.
And (only when needed) you can use
Queue1 = new BlockingCollection<MyItemType>(MaxItems)so that this queue doesn’t overfill.