4

I have a conundrum. I have "inherited" a very badly designed and very complex system which I am modernizing and rebuilding (with my team) piece-by-piece. The problem is that the current system is depended on by 200+ users, and they are having major issues with performance due to the (lack of) design. The most problematic issue at the moment is that a significant amount of work is beng placed on the UI thread, which leads to the GUI hanging until the thread is cleared amd message pumping can continue. Much of this work actually does need to be on the GUI thread, as it is updating a large number of fields in a grid due to other calculation results on other threads.

The issue is this: I do not have the resource to dedicate to re-writing the threading model and underlying classes involved here, and the complexity of that work would introduce significant risk which is unacceptable to my client.

I wanted to know if anyone had any suggestions on how to make the UI more performant, without interfering too much with the currnet threading model.

My initial thought is that there might be some way to put a "buffer" in front of the actual invokes to the UI thread to make sure that the GUI does not get overloaded, or when it does to back off the dispatches to it.

Any suggestions would be greatly appreciated.

I know none of this is ideal, but we are where we are, and I really want to give my users a better experience prior to the year-long re-write's completion!

Thanks!

Update #1 This is a winforms app...sorry this wasnt clear at the outset. New code is WPF, but these modules are winforms.

Update #2 I am thinking I may initially try changing most BeginInvoke calls to the UI thread to Invoke, introducing a serialization that will hopefully increase UI responsiveness. Any (non-obvious) downsides here, that anyone can forsee?

miguel
  • 2,961
  • 4
  • 26
  • 34
  • Making the asynchronous calls synchronous is effectively moving back to a single-threaded approach. Invoke blocks the calling thread until the UI thread updates, so you won't gain anything. (Of course, if all the invoke calls are coming from the UI thread anyway, you won't lose anything either.) – Greg D May 02 '11 at 22:02

3 Answers3

1

I don't know if it will work in your particular case, but I've been in a similar (though probably less stressful) situation in the past, and I came up with some code that let me marshal more-or-less arbitrary code from a background thread to the UI thread. This was written in the era of WinForms.

This may provide a lower-risk technique for you to throw some computation back on some background threads and marshal your UI updates more easily to the foreground without a full re-architecture of the threading model (or lack thereof).

Regarding performance of the UI, sometimes the best way to make it faster is to do less. The application I was working on when I created that linked code was parsing hundreds of megabytes of strings to manually munge some xml. Replacing immutable strings with stringbuilders took the operation from an OOM failure condition to completion within 5 minutes of wall-clock time. Then I noticed that, for every element it parsed, it updated the UI. I tweaked that code to update the ui every 50 elements or so, and that took it down to a fraction of a minute. Turning off all UI updates knocked the time down to a few seconds.

Have you considered not-updating the UI until the calculations are complete, perhaps just running a progress bar instead? One other possibility (off the top of my head, dunno how evil it is) would be to queue updates as lambdas in a dictionary that is keyed off of the control getting updated. That way you could replace redundant updates to values if a single control is updated multiple times, e.g. (This assumes that the app isn't reading values directly from the UI to perform calculations, of course. Which it probably is. :( )

Community
  • 1
  • 1
Greg D
  • 43,259
  • 14
  • 84
  • 117
0

If you are doing massive updates you could suspend and resume the binding in your controls, using the SuspendBinding and ResumeBinding methods of the CurrencyManager. This way if something couldn't be moved in a separate thread because of massive UI interaction, you can still have some performance gain by not showing the updates. Also suspending any list change notification up to the end of the updates might help, especially when using grids or other controls.

LazyOfT
  • 1,428
  • 7
  • 20
  • I think those are methods on the base class: http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingmanagerbase.aspx :) – Greg D Apr 28 '11 at 21:27
0

Assuming this is a WinForms application, one fairly hackish approach is to call Application.DoEvents() periodically inside your intensive work that's running on the UI thread. This allows the message pump to process pending messages in the midst of your logic. Note that this is fraught with its own set of perils, such as:

  1. All UI events can fire, which means you have have things like button presses triggering long-running UI updates, which subsequently block your previous UI work that yielded with DoEvents(). A particularly insidious form of this is when you 'double click' a Button, which spins up a long operation, which yields to DoEvents(), which handles another button click and spins up the same operation, which runs until completion, then returns control back to the first button click handler in the middle of its operation. What this means is that you have to be very careful with what UI interaction you allow during these 'long-running' operations on the UI thread.

  2. Because the user can interact with the UI, it's possible they can invalidate assumptions that your code previously made about things not changing. Again, it's critical to limit what the user can do so that they can't invalidate the state your operation depends on. Of course, you run into the same problem with code running in another thread.

  3. This was a fairly common technique back in the days of VB6 (where multi-threading was quite difficult to do), but it's frowned upon in modern development. It might get you out of a tight spot in your legacy application, but I recommend flagging it with //HACK tags for future cleanup.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • interesting...i like the simplicity, might be nice to try initially before moving onto a more complex solution (maybe a modification of Greg's idea)..i have a feeling the interaction will cause this to be a non-goer though. pity. – miguel Apr 28 '11 at 20:34
  • 1
    It's a dangerous solution precisely because you can 'fix' the behavior in a matter of minutes, but there is still a lot of complexity to be managed if you want it to work safely. You will encounter the same complexity with other, more elaborate, solutions, but at least in those cases the complexity is more apparent up-front. – Dan Bryant Apr 28 '11 at 21:01
  • Yes, Application.DoEvents() is dangerous business. Be warned that it may result in unexpected re-entrancy, especially in the scenario of a poorly-designed system. – Greg D Apr 28 '11 at 21:25