Big Ball of Mud

Thread Marshalling Part 1 – creating a thread in Windows Forms

with 2 comments

Note: Even if you know basics for spawning the new thread in the UI, I recommend performing the following exercise if you never did.

Note: The solutions from this post is not the way how it is done in the 21st century. Stay focused for next parts.

Common situation in Windows Forms development: suppose we want to execute a long-running task, which takes a lot of CPU. The most CPU intensive task I know is running an empty loop:

    for (long i = 0; i < 1000000000; i++)
    {
        // DON'T DO THIS AT HOME
    }

Creating empty loops is a wasteful for both CPU and developer, so lets create something really fancy, seasoned OO professional style:

    
    1     public class EmptyLoop
    2     {
    3         private readonly long _count;
    4         private readonly long _step;
    5         private bool _cancelled;
    6
    7         public EmptyLoop(long countTo)
    8         {
    9             _count = countTo;
   10             _step = countTo / 100;
   11         }
   12
   13         public void Go()
   14         {
   15             _cancelled = false;
   16             for (long i = 0; i < _count; i++)
   17             {
   18                 if (_cancelled) break;
   19                 if (i % _step == 0) OnUpdateProgress((int)(i / _step));
   20             }
   21
   22             OnCompleted(_cancelled);
   23         }
   24
   25         public void Cancel()
   26         {
   27             _cancelled = true;
   28         }
   29
   30         protected virtual void OnUpdateProgress(int percent)
   31         {
   32             if (UpdateProgress != null)
   33                 UpdateProgress(this, new UpdateProgressEventArgs(percent));
   34         }
   35
   36         protected virtual void OnCompleted(bool cancelled)
   37         {
   38             if (Completed != null)
   39                 Completed(this, new CompletedEventArgs(cancelled));
   40         }
   41
   42         public event EventHandler<UpdateProgressEventArgs> UpdateProgress;
   43         public event EventHandler<CompletedEventArgs> Completed;
   44
   45         public class UpdateProgressEventArgs : EventArgs
   46         {
   47             public int Percent { get; private set; }
   48
   49             public UpdateProgressEventArgs(int percent)
   50             {
   51                 Percent = percent;
   52             }
   53         }
   54
   55         public class CompletedEventArgs : EventArgs
   56         {
   57             public bool Cancelled { get; private set; }
   58
   59             public CompletedEventArgs(bool cancelled)
   60             {
   61                 Cancelled = cancelled;
   62             }
   63         }
   64     }

Maybe not so empty now, but you’ve got the point. We have an algorithm, what we need is a form for running it:

multithreading1_form

The code for the form:

    1     public partial class Main : Form
    2     {
    3         private readonly EmptyLoop _emptyLoop = new EmptyLoop(1000000000);
    4
    5         public Main()
    6         {
    7             InitializeComponent();
    8         }
    9
   10         private void bStart_Click(object sender, EventArgs e)
   11         {
   12             progressBar.Visible = true;
   13             bCancel.Enabled = true;
   14
   15             _emptyLoop.UpdateProgress += emptyloop_UpdateProgress;
   16             _emptyLoop.Completed += emptyloop_Completed;
   17
   18             _emptyLoop.Go();
   19         }
   20
   21         private void ShowProgress(int percent)
   22         {
   23             progressBar.Value = percent;
   24         }
   25
   26         private void ShowAlgorithmCompleted()
   27         {
   28             _emptyLoop.UpdateProgress -= emptyloop_UpdateProgress;
   29             _emptyLoop.Completed -= emptyloop_Completed;
   30             progressBar.Value = 0;
   31             progressBar.Visible = false;
   32             bCancel.Enabled = false;
   33         }
   34
   35         private void emptyloop_UpdateProgress(object sender, EmptyLoop.UpdateProgressEventArgs e)
   36         {
   37             ShowProgress(e.Percent);
   38         }
   39
   40         private void emptyloop_Completed(object sender, EmptyLoop.CompletedEventArgs e)
   41         {
   42             ShowAlgorithmCompleted();
   43         }
   44
   45         private void bCancel_Click(object sender, EventArgs e)
   46         {
   47             _emptyLoop.Cancel();
   48         }
   49     }

When we run this simple program, something weird happens (your results may vary): the program seems to do its job by running the loop, but the progress bar is not updated smoothly and when you try to click the Cancel button, the application hangs.

When we realize that each windows form is something of a loop itself, the problem becomes clear – we do not allow the form to update itself by freezing the message loop. Maybe allowing the form to update from time to time will solve the issue:

        private void ShowProgress(int percent)
        {
            progressBar.Value = percent;
            Application.DoEvents();
        }

Better, but no banana. Now Cancel button sometimes work, but the UI still does not react properly and usually the operation completes before the progress bar is filled (wait, haven’t I seen this before?). Clearly the UI is still not refreshed properly. If only we could have a mechanism which would allow to switch from the running algorithm to the UI.

Wait, isn’t it what threads suppose to do? Well they should: it is an abstraction which allows to run things in parallel or just give us the impression of doing it at the level of the system. Let’s remove the DoEvents call and try to run the loop on another thread:

        private void bStart_Click(object sender, EventArgs e)
        {
            progressBar.Visible = true;
            bCancel.Enabled = true;
            _emptyLoop.UpdateProgress += emptyloop_UpdateProgress;
            _emptyLoop.Completed += emptyloop_Completed;
            var loopThread = new Thread(_emptyLoop.Go);
            loopThread.Start();
        }

Oops! As soon as we click the Start button we get InvalidOperationException in ShowProgress method:

        Cross-thread operation not valid: Control 'progressBar' accessed from a thread other than the thread it was created on.

Let me explain what is happening here. When we start the program, the main thread is created for us and this is the thread on which the form and all its controls are run. When we click the Start button, we spawn a new thread in which our empty loop is running. The EmptyLoop class has two events: UpdateProgress and Completed, to which the Form subscribes, so when we run the EmptyLoop method, it occasionally calls two event handlers emptyLoop_UpdateProgress and emptyLoop_Completed. As the EmptyLoop.Go method is called on another thread, so are event handlers. But what event handlers want to achieve is accessing the controls of the form to provide feedback to the user. WinForms controls to be handled properly need to be run only in one thread – the GUI thread. What we need is to jump for a moment to the UI thread for a moment to update the controls. This is called thread marshalling.

Each control has two methods for this: Invoke and BeginInvoke.

        private void algorithm_UpdateProgress(object sender, EmptyLoop.UpdateProgressEventArgs e)
        {
            Invoke(new Action<int>(ShowProgress), e.Percent);
        }
        private void algorithm_Completed(object sender, EmptyLoop.CompletedEventArgs e)
        {
            Invoke(new Action(ShowAlgorithmCompleted));
        }

It does exactly what we need: calls the method on the UI thread. Here we use the methods of the form, but any control will do.

If you need to check from which thread the method is accessed, use InvokeRequired property:

        if (!InvokeRequired)
        {
            ShowProgress(e.Percent);
        }
        else
        {
            Invoke(new Action<int>(ShowProgress), e.Percent);
        }

The difference between Invoke and BeginInvoke is that the computing thread will stop on Invoke until the ShowProgress completes, while BeginInvoke is asynchronous – the computing thread will will just fire and forget ShowProgress and continue its worthless loop spinning.

Advertisements

Written by bigballofmud

2009/03/21 at 8:51 pm

Posted in C#, Multithreading

2 Responses

Subscribe to comments with RSS.

  1. Hello.

    As you’ve previously said, _emptyLoop.UpdateProgress event is executed in not UI thread. So to make my application work i’ve to add Invoke method in event handler to marshall the call to UI thread.

    BackgroundWorker class does almost the same but event OnProgressChanged is called from the UI thread, so there is no need to add Invoke functionality to handler. So the marshalling is somehow occurs inside BackgroundWorker class. It somehow knows what thread is UI thread and executes event calls on it.

    I wonder how it works.
    The goal is to make a class so, that this example works well.

    35 private void emptyloop_UpdateProgress(object sender, EmptyLoop.UpdateProgressEventArgs e)
    36 {
    37 ShowProgress(e.Percent);
    38 }

    Xeenych

    2009/04/09 at 7:37 am

    • @Xeenych, please take a look at next posts from this short series, it is exactly what you need 😉

      I will add links to next parts, so it will be easier to navigate … Done.

      bigballofmud

      2009/04/09 at 7:09 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: