News:

Choose a design and let our professionals help you build a successful website   - ITAcumens

Main Menu

Accessing controls from a worker thread

Started by dhilipkumar, Mar 27, 2009, 03:38 PM

Previous topic - Next topic

dhilipkumar

Accessing controls from a worker thread

Windows applications run on multiple threads. There is one very important that takes care to handle the user interface. It takes care to update it (along with its controls), to repaint it, and so on. This is called the UI thread.


Other threads (worker threads) can be used to perform other tasks. Tipically you place there some long running operations, or maybe some intensive CPU operations, that if done by the UI thread would lead to its freezing or to that [not responding] text you get written onto the form's Text bar when an application hangs.
Programming a multithreaded application can be very tricky, for reasons we're not going to explore here. In this article we'll just focus on one single problem you may encounter when accessing controls from within a worker thread.
Operating systems are based on rules and one windows rule is this:

You shalt operate on a window ONLY from its creating thread.

Why this? Simple. Reading and/or writing control properties from different threads could lead to unpredictable results if more than one thread could access the control. Picture this situation:
The UI thread is trying to write some value into a textbox. Before it can complete the operation, it is preempted and some CPU time is given to another thread that tries to read from the textbox. The textbox could be in an inconsistent state, therefore the worker thread could read an incomplete/inacceptable value from it and this could cause serious problems to your application.

So, what do we have to do to solve this problem?
First we must be familiar with delegates, then we'll see the solution.

A delegate is basically a method that calls another method, eventually passing it some parameters. It is like when you ask your secretary to call one of your customers. You just call the delegate (the secretary) and she calls the method (do the phone call). Calling directly the method is like for you to directly make that phone call.
Delegates are very useful, expecially in this situation.
To come back to our problem, we can solve it like this: from a worker thread, instead of calling some method to access (read/write) a control's property we just make the UI thread call some delegate and that delegate will take care of calling the desired method.
This way, even if the first call is made within the worker thread, we're sure that it will be the UI thread that will do the job.
We cannot call directly the delegate, so we'll make the form invoke it by calling the form's Invoke() method.
So, asking the form to invoke the delegate we can safely modify any control's property from within any worker thread, because the operations will be done by the UI thread, respecting the rules.

We can accomplish this in several ways. For example we can set up a delegate, make it point to the desired method and then, from within the worker thread, we can ask the form to invoke it. The form will make the call to the delegate and the delegate will then call the method.
This is good, but not my favourite approach.
I like very much to write smart methods so I'll try to explain here how to write a method that can decide when it can execute the operations it has been written for or when it has to wrap itself into a callback like I have described above.

The best way to understand a concept is always to see an example. I have made up a form with a label (clockLbl), a button (startBtn) and a method that takes a string as parameter and sets the label's text to that string, called SetClock.
The reason for this name is that my worker thread is going to update the label's text once a second, with the current date and time string representation.
An helper ClockRun method is the one that will be executed by our worker thread.

Skim the code and then read the explanation:

// this is the delegate declaration
delegate void SetClockCallBack(string time);

// smart method to set the label's text
public void SetClock(string datetime) {
    if (this.InvokeRequired) {
        SetClockCallBack callback = new SetClockCallBack(SetClock);
        this.Invoke(callback, datetime);
    } else {
        this.clockLbl.Text = datetime;
    }
}

// click button handler
private void startBtn_Click(object sender, EventArgs e) {
    startBtn.Enabled = false;
    Thread t=new Thread(new ThreadStart(ClockRun));
    t.IsBackground = true;
    t.Start();
}

// helper method
private void ClockRun() {
    try {
        while (true) {
            // we'll just call the SetClock method here, no invoke
            this.SetClock(DateTime.Now.ToString());
            Thread.Sleep(1000);
        }
    } catch (Exception ex){
        // nothing is done here
        System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
}

As you can see, there is the declaration of the delegate we need. We're calling it SetClockCallBack and we're also telling the compiler that the method associated to this delegate will require a string in input. When the user clicks on the start button, the startBtn_Click method is called. This method disables the start button and sets up a new thread t, preparing it to run the ClockRun method.

I set t as a background thread, which will make it not to cause problems when the application closes, since every background thread dies along with the main one.
After the thread t (our worker thread) has started, the ClockRun method takes action. Forget about the try{} catch{} block here (explained later). What this method does is simply to call directly the SetClock method, passing it the current datetime string representation. After each call, the thread sleeps for one second, and then again it calls the SetClock method, and so on.

You are wondering why I didn't use Invoke(), since I have said that we need to invoke any call the the SetClock method if we're calling it from outside the UI thread.
The reason I can avoid calling invoke here is that my SetClock method is smart enough to call it itself. As you can see, when the SetClock method is called, the first thing it does is to check if InvokeRequired is true. InvokeRequired returns true when we call that method from outside the UI thread. So, the first time, InvokeRequired is true and the method knows that it can't run. A new instance of a SetClockCallBack is then made, called simply callback, and it is assigned to the method itself. This means that callback is a delegate pointing to the SetClock method. After callback has been initialized, it is invoked properly and passed the datetime string.

What happens now? As soon as the UI thread will have some CPU time, it will invoke callback and callback will then call SetClock. Being called by the UI thread, this time InvokeRequired will return false and the method will be able to set the clockLbl text to the datetime string. Fells like a recursive call, but it's not properly recursion.

It would have been a little simpler just to write a simple SetClock method, setup a delegate and invoke it when necessary, but this approach can lead to bugs. You can forget to use Invoke() somewhere and you could also misplace the delegate instantiation, doing it after a call to it has been already done. Bugs like these are very difficult to spot because you don't get errors or warnings at compile time.
Using the approach I propose, you just call directly the method and don't ever worry about it, because it can take care of itself and eventually set up a delegate to invoke.

The reason for that try{} catch{} block in the ClockRun method is that if you close the application when the worker thread is updating the label, it may find that the label has already been disposed, throwing an exception. We don't care about this to happen in this case, and just write the exception to the debug console.
In a different situation, maybe when it comes to write a real application, you should better take care of these problems and worry to handle thread terminations properly.

OSIX