Maintaining a Responsive UI, Page 2
Thou Shalt Not Do UI From Just Any Thread
Windows Forms are a managed API for doing UI, and a pretty good one at that. But still, Windows Forms are just a wrapper around existing Windows UI concepts, and for reasons related to the Windows messages and message pumps you should never call methods or properties on your GUI objects from just any thread. Here comes the rule.
Important Rule — You should only access objects derived from |
Generally, this means that your main thread is the only thread you should use to enable and disable controls, populated lists, etc. This rule has three exceptions, which I will discuss in a moment, but first let's pay a little more attention to the rule.
The gist of the rule is that windows and controls are owned by threads. This means some operations will only work when performed from the owning thread. Additionally, the Windows Forms classes occasionally destroy controls and windows under the covers, only to rebuild them without the programmer or end-user ever being the wiser. But if code calls a method on a control from a background thread then this destruction and reconstruction process will occur on that background thread. This will have the effect of alienating the control from the message pump that is still running on the main UI thread. This is not a good thing.
The solution is to make sure that when your background processing has finished, you somehow let the main thread know to finishing work. The finishing work is often a few control-enablings, and perhaps the population of a list, table or textbox with a result from the background operation. And how does the background thread get this work to occur on the main thread? Remember those three exceptions to the rule I mentioned? The
BeginInvoke methods on
Form (or the base class
Control) are methods that are meant to be called from any arbitrary thread. In fact, the job of these methods is to call a method, through a delegate object, using the thread that pumps messages for the
Control-derived object on which you are calling
Exceptions to the Rule — The |
If this all seems a little blurry, check out the
SetDoingLengthyOperation method in Figure 3, to help clear things up. This method's job is to enable or disable pieces of the UI depending on whether a lengthy operation is just about to start, or has just finished. But notice also that the
SetDoingLengthyOperation method also performs some upfront work to see if it needs to hop on over to the UI thread before it does its real job. The way that it does this is by calling the property
InvokeRequired is true, then you are processing on the wrong thread for doing UI, and you need to do your business via a call to Invoke on the
Control with which you are working.
SetDoingLengthyOperation does just this.
The benefit of capturing the work of invoking across thread boundaries in the method itself is that now you can make calls to the method from any other code, running on any other thread, and the method knows just what to do if it needs to change threads before doing its work. And that's it; that is the last step in the process of making an application both responsive while still following the threading-related rules of Windows Forms objects.
Some Parting Thoughts
The techniques used in this article can be a little bit tough to grasp at first, but they are widely reusable techniques that can really improve the user experience with your applications. Building and running the code, as well as walking through the examples will help out in understanding the techniques.
There is certainly room for enhancement to the simple examples I show in this article. Two possible enhancements that come to mind right away are a progress meter and the ability to make operations cancelable. Both of these topics are weighty enough together to deserve another article. However in the code samples that I put together for this piece, I did make a fourth revision of the ResponsiveUI application that supports a simple cancel operation. Perhaps you will find it helpful in getting started with some more advanced responsive UI techniques.
Thread correctness is another consideration, which peripherally relates to cancellation. In general most applications aren't complex enough to warrant multiple threads running at once, and that is good. As soon as you are actively processing on multiple threads, then you have to do some form of thread-synchronization to keep things in tact. Part 2 of the sample code suffered from unrestrained multithreading. But our final revision did not.
Even though our Part 3 application is technically multi-threaded, in practice it is still runs its business logic single-threaded. Sure the UI thread will pump messages while the background thread is processing, but the UI thread, because of the disabled UI, refuses to do any business logic while the background processes. This is key to the application's success. You should try to stick as close to this design as you can while remaining responsive at all times, this will greatly simplify the amount of thread-synchronization logic that you have to build into your application.
Well, that should be sufficient food for thought for now. Well wait! One more extra: along with the C# samples parts 1 through 4; I've also included a part-4 version in Visual Basic .Net as well. So regardless of your preferred .NET language, you can dig through the sources and try this stuff out. I hope you've find it useful.
Download Source Code
Download source code: ResponsiveUI.zip - 12kb
About the Author...
Jason Clark has been banging code since he fell in love with computers way back in sixth grade. Since the early nineties, Jason professional life has been devoted to Windows development. His most recent full-time employment was with Microsoft, where he wrote security protocols for the Windows operating systems.
Jason now does software consulting and writes about a variety of topics ranging from developing secure software to writing software that runs on Microsoft's new .NET platform. Jason coauthored Programming Server-Side Applications for Microsoft Windows 2000, and he writes articles for Dr. Dobbs Journal, MSDN Magazine (formerly MSJ), Windows Developers Journal, and other leading developer magazines. Jason's commercial software credits include work he has performed for Microsoft, IBM, Sony, HP, and other companies, and involve everything from writing printer drivers to helping develop the Windows 2000 and Windows Server 2003 operating systems.
# # #