Windows UI Threading
- January 10th, 2010
- Posted in DotNet
- Write comment
Using background threads to handle long running processes such as fetching data from the db should be common practice when developing windows forms. Failure to do so will result in a clunky UI that will lock up and may appear as crashed to the user. It also allows you to do nice things like updating progress bars etc.
A common trap is trying to call a method which updates a UI control from a background thread. This will result in the “Cross Thread Call” exception which can frighten many developers away from threading in the IU. Here is a pattern I tend to follow when threading UI that takes a lot of the mystery out of things and keeps the UI running smoothly.
First we have a method that calls a long running process. In this method we simply create a new thread in which to run the process and get it started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /// <summary> /// Do some long running process on a background thread /// </summary> private void Start() { lbl.Text = string.Empty; Thread worker = new Thread(DoSomething); worker.Start(); } /// <summary> /// Long running process /// </summary> private void DoSomething() { for (int i = 0; i <= 100; i+=5) { UpdateProgressBar(i); Thread.Sleep(500); } } |
In each iteration of the loop we update the value of a progress bar. This however can only be achieved on the UI thread and this method was called on the background thread. Normally this would throw an exception as the invoke keyword is not used but because of the way the UpdateProgressBar method has been written this is not an issue.
The code below shows how to write any method that updates a UI control so that it can be called safely from any thread and will always execute on the UI thread no matter what the thread the caller is on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /// <summary> /// Something that updates a UI control that /// might be called from a background thread /// </summary> /// <param name="progress"></param> private void UpdateProgressBar(int progress) { //Wrap any code that updates a UI control in this anonymous delegate Func<int, object> work = delegate(int progressCount) { if (progressCount < 0) progressCount = 0; if (progressCount > 100) progressCount = 100; progressBar1.Value = progressCount; if (progressCount == 100) lbl.Text = "Done!"; return null; }; //Check if we are running on a background thread and if so use Invoke //otherwise just call the delegate if (this.InvokeRequired) Invoke(work, progress); else work(progress); } |
The Func is an anonymous delegate … all logic that affects controls should be wrapped in this. The last two lines handle the differing threads. InvokeRequired determines if we are on the UI thread or the background thread. If we are on a background thread it will return true and we call Invoke on the anonymous method to ensure it is executed on the UI thread. If we are already on the UI thread the method is just called without Invoke.
By structuring the control update methods this way they can be called from any thread and will never throw an error due to cross thread calls.
So you decided that the only way to communicate with your fellow gigs is through a blog :0) Ok let’s talk then