Best practice for updating UI in multi-threaded app
What is the best way to update many UI elements from many other threads, in a multi-threaded Windows desktop app?
Currently, I use delegates, which creates many temporary threads. Should the UI be on its own thread rather than the "main" thread? Would a queue pattern be better?
Well, the UI thread is the main thread, so you can't really make it another thread. I would assume (but don't know for sure) that when you do Control.BeginInvoke or similar, it doesn't create another "temporary thread" since the whole thing with controls is that anything that changes them has to run on the main thread. As far as running a bunch of temporary threads, that's what the ThreadPool was designed to do, so using it should be just fine.
There is a world of difference between performing BeginInvoke on a delegate, and performing it on a control (passing a delegate as a parameter). IMHO, using the same name for both methods was a mistake, but what's done is done.
Performing BeginInvoke on a control will not create a new thread, nor take one from the thread pool, but will instead post a message in the control's UI thread's event queue. There is no particular limit to the number of messages that can be posted in the event queue, but one should nonetheless limit the number of unprocessed messages one will post to the event queue. Otherwise, if the UI thread gets blocked for some reason, such messages may accumulate without bound.
My particular coding style is to use an UpdateNeeded variable (declared as Integer, but functionally a Boolean) which is set to 0 at the beginning of the control's update routine. When I'm considering updating a control, I Threading.Interlocked.Exchange the variable to 1 and see if it wasn't already 1. If so, I perform a BeginInvoke on the control. If the BeginInvoke fails, I set the variable to 2 (so the control's update routine will know that the control needs updating, but other attempts to update the control will retry the BeginInvoke).
This style assumes that (1) if there are many changes to a control's value before an update takes place, all but the last should be ignored; (2) it is acceptable to update the fields associated with a control while it's being drawn, provided that one performs another update after the current one completes; (3) a redundant control update (e.g. because a field was changed after the update routine started to draw a control, but before the field's value was sampled) will be harmless; (4) updating controls every time the value changes will not cause excessive CPU loading.
If these assumptions hold, the code is fairly straightforward. If they do not, things are more complicated.