Search code examples
c#multithreadinguser-interfaceworker

How to update GUI thread/class from worker thread/class?


First question here so hello everyone.

The requirement I'm working on is a small test application that communicates with an external device over a serial port. The communication can take a long time, and the device can return all sorts of errors.

The device is nicely abstracted in its own class that the GUI thread starts to run in its own thread and has the usual open/close/read data/write data basic functions. The GUI is also pretty simple - choose COM port, open, close, show data read or errors from device, allow modification and write back etc.

The question is simply how to update the GUI from the device class? There are several distinct types of data the device deals with so I need a relatively generic bridge between the GUI form/thread class and the working device class/thread. In the GUI to device direction everything works fine with [Begin]Invoke calls for open/close/read/write etc. on various GUI generated events.

I've read the thread here (How to update GUI from another thread in C#?) where the assumption is made that the GUI and worker thread are in the same class. Google searches throw up how to create a delegate or how to create the classic background worker but that's not at all what I need, although they may be part of the solution. So, is there a simple but generic structure that can be used?

My level of C# is moderate and I've been programming all my working life, given a clue I'll figure it out (and post back)... Thanks in advance for any help.


Solution

  • You can expose a public method on your UI class that the device class can call on the background thread with all the information it needs to pass to the UI. That public method will be executed in the context of the background thread, but since it belongs to the UI class, you can now employ any of the call marshaling techniques you've read about.

    Thus, the simplest design then would be:

    • add a method to your UI class (for example MyUIForm)called something like UpdateUI() that takes whatever data structure you are using to pass the data from the device to the UI you use. You can declare that method in an interface (for example IUIForm), if you want to support DI/IoC later, and have the form implement it.
    • on thread A (the UI thread), your UI class creates the device class, initializes all the necessary settings and starts its background thread. It also passes a pointer to itself.
    • on thread B, the device collects the data and calls MyUIForm.UpdateUI() (or IUIForm.UpdateUI()).
    • UpdateUI does Invoke or BeginInvoke as appropriate.

    Note that has the side benefit of encapsulating all the UI and presentation logic in your UI class. Your device class can now focus on dealing with the hardware.

    Update: To address your scalability concerns -

    No matter how much your app grows and how many UI classes you have, you still want to cross the thread boundary using the BeginInvoke for the particular UI class you want to update. (That UI class might be a specific control or the root of a particular visual tree, it does not really matter) The main reason is if you have more than one UI threads, you have to ensure the update of any UI happens on the thread this particualr UI was created on, due to the way Windows messaging and windows work. Hence, the actual logic of crossing the boundary thread should be encapsulated in the UI layer.

    You device class should not have to care what UI classes and on which thread need to be updated. In fact, I personally would make the device fully ignorant about any UI and would just expose events on it that different UI classes can subscribe on.

    Note that the alternative solution is to make the threading fully encapsulated in the device class and make the UI ignorant about the existence of a bacground thread. However, then thread boundary crossing then becomes responsibility of the device class and should be contained within its logic, so you shouldn't be using the UI way of crossing the threads. That also means your device class is bound to particular UI thread.