0

I should report several certain things to my GUI while another thread is running in the background, such as:

  • Progress Value
  • Elapsed Time
  • Number of Results Found in real-time
  • Number of Errors Occurred during the process
  • and so on

I can use this piece of code when I need to invoke the UI and change something:

    private void DoInvoke(Action action)
    {
        try
        {
            if (InvokeRequired)
                BeginInvoke(action);
            else
                action();
        }
        catch { }
    }

It works well, GUI and background thread work very well and the info will be reported and shown in UI. But there is a problem, because of so many contexts changing between the background thread and UI, the CPU usage will be very high! I need to update the UI values without this context changing and without CPU usage.

So I decided to make a class of needed values and send it to the background thread. so It is a reference in which both UI and background thread can access it.

and I have put an event handler inside the class, so whenever a value is changed it will invoke. in UI I have attached an event to this handler, so every time a value is changed in this class, the UI should update that value. But again I will face cross-thread error. How to handle such a thing? I do not want the high cpu usage and also I need real-time UI update.

1 Answer 1

1

There are various ways to approach this.

The first thing is to define "realtime". If your data changes every 1 millisecond, even if you were able to update the UI that fast, noone would be able to see it. As a guideline, we can only detect changes at around 60Hz (that's why videogames target that framerate). In practice, you probably want the UI to update in the 10-50Hz range.

The timer solution

A simple solution, which may or may not be appropriate, would be to setup a timer on the UI thread that fires at the appropriate rate and update your controls in the timer event handler.

The Invoke() / BeginInvoke() solution

Another option is to still use the BeginInvoke() approach, but:

  1. Implement the logic to update all controls in a single function and only BeginInvoke() that one, so you only queue a single work item in the UI thread. If you were to do a BeginInvoke() for each control, you'd cause a context switch for each control.

  2. Skip invoking a BeginInvoke() if a minimum time has not elapsed since the last update. For instance, if data has changed after 3 milliseconds, you could skip all updates until one happens after 50 milliseconds (that would give a max update rate of 20 Hz).

The complications

This will work fine if you have simple controls, however you could run into issues if you have complex ones, like graphs, of many many controls to update. In this case, it may take a long time to redraw them, so you could not be able to update the UI at the desired rate. In you BeginInvoke() too often and the UI thread can't keep up, the app will essentially freeze because it doesn't have time to handle the user input.

There could be other conditions that lead the main thread to be more busy than usual (resizing the window or other processing that takes max a couple of seconds and you didn't bother to run in a separate thread).

So, in my programs, I usually set a flag immediately before I call BeginInvoke(), and I clear it in the invoked function. The next time I have to call BeginInvoke(), I first check the flag. If it's still set, it means the UI thread was busy and still hasn't managed to update the UI. In that case, I skip the BeginInvoke().

Finally, if you have a lot of stuff going on (I had to update many graphs and views) you may also need to have your logic guarantee a minimum time from when the update code in the UI thread ends executing and when you queue a new update from your background thread. This guarantees there's some time left in the UI thread to process user input, while the thread is very busy updating the UI in the rest of the time.

Final notes

If a value has not changed, you want to avoid redrawing the relative control, because it's pointless. I expect most WinForms controls, like a label, to already not redraw if you set their Text to the same value they already have, but if you have custom controls, third party controls, or do things like clear a ListView and repopulate it, you want to make sure the code isn't causing a redraw when it's not needed.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.