UI dispatching buffering?

I have a puzzle. I “inherited” a very poorly designed and very complex system, which I modernize and rebuild (with my team) in parts. The problem is that the current system has more than 200 users, and they have serious performance problems due to (lack of) design. The most problematic problem at the moment is that a significant amount of work is placed in the user interface thread, which leads to a hang of the graphical interface until the thread is cleared. Most of this work is really needed in the GUI thread, because it updates a large number of fields in the grid due to different calculation results for other threads.

The problem is this: I do not have the resource to re-write the thread model and base classes used here, and the complexity of this work will lead to a significant risk that is unacceptable to my client.

I wanted to know if anyone has any suggestions on how to make the user interface more efficient without affecting the threadnet thread model too much.

My initial thought was that there might be some way to put a “buffer” in front of the actual calls in the UI thread, to make sure the GUI is not overloaded or when it does, to cancel sending it.

Any suggestions would be greatly appreciated.

I know that none of this is perfect, but we are where we are and I really want to give my users the best experience before completing the re-recording in a year!

Thanks!

Update # 1 This is a winforms application ... sorry, this was not clear from the start. The new code is WPF, but these modules are winforms.

Update # 2 I think that I will initially try to change most of BeginInvoke calls to the user interface thread for Invoke by introducing serialization, which we hope will increase the responsiveness of the interface. Any (non-obvious) flaws here that everyone can see?

+4
source share
3 answers

I don’t know if this will work in your particular case, but I was in a similar (although probably less stressful) situation in the past, and I came up with some code that allow me to march more or less arbitrary code from the background thread into user interface thread. It was written in the era of WinForms.

This can provide a lower level of risk so that you can drop some calculations onto some background threads and make it easier to upgrade your user interfaces to the forefront without completely restructuring the streaming model (or lack thereof).

Regarding UI performance, sometimes the best way to do it faster is to do less. The application that I worked on when I created this related code processed hundreds of megabytes of lines to manually translate some xml. Replacing immutable strings with string builders moved the operation from the OOM failure condition to completion within 5 minutes of the wall clock. Then I noticed that for each element that he analyzed, he updated the user interface. I changed this code to update ui every 50 elements or so, and it was reduced to a split second. Disabling all updates to the user interface took a few seconds.

You thought you weren’t updating the user interface until the calculations were completed, maybe just start the progress bar? Another possibility (from my head, not knowing how evil it is) is to queue updates in the form of lambdas in a dictionary that is blocked by updating the control. Thus, you can replace redundant updates with values ​​if one control is updated several times, for example. (This assumes that the application does not read the values ​​directly from the user interface to perform the calculations, of course. Which is probably this. :()

+1
source

If you are doing bulk updates, you can pause and resume binding in your controls using the CurrencyManager's SuspendBinding and ResumeBinding methods. Thus, if something cannot be moved in a separate thread due to a lot of interaction with the user interface, you can still get some increase in performance without showing updates. Also, pausing notification of a list change until the end of updates can help, especially when using grids or other controls.

0
source

Assuming this is a WinForms application, one pretty hacky approach is to periodically call Application.DoEvents () inside your intensive work that is done on the User Interface. This allows the message pump to process pending messages in the middle of your logic. Please note that this is fraught with its own set of dangers, for example:

  • All UI events can be triggered, which means that you have things like button presses that trigger continuous UI updates, which subsequently block the previous UI operation that is given using DoEvents (). A particularly insidious form of this is that you press the Button button twice, which drags out a lengthy operation, which gives DoEvents (), which processes another click on the button, and twists the same operation that runs until completion, and then returns control back to the first button click handler in the middle of its operation. This means that you have to be very careful what kind of user interface interaction you allow during these “lengthy” operations on the user interface thread.

  • Since the user can interact with the user interface, perhaps they can invalidate the assumptions your code previously made about things that don't change. Again, it is important to limit what the user can do so that they cannot invalidate the state that your operation depends on. Of course, you are facing the same problem when the code runs on a different thread.

  • It was a fairly common technique back in the days of VB6 (where multithreading was a pretty difficult task), but in modern development it has frowned. This may cause you to lose space in your legacy application, but I recommend tagging it with //HACK tags for future cleanup.

0
source

All Articles