logo

Tools for Updating Windows Forms UI from Background Threads

By Peter Bromberg
Printer Friendly Version
View My Articles
1566 Views
    

You too could earn $100 for writing an article for EggHeadCafe.

One of the most common frustrations of new .NET programmers revolves around an incomplete understanding of how the Windows Forms UI model works. This shows how to use the Control.Invoke method as well as the BackgroundWorker class to perform updates to the UI from a secondary thread.



One of the most common frustrations of new .NET programmers revolves around an incomplete understanding of how the Windows Forms UI model works. A very simple example, to illustrate, comes from a recent post on one of our Eggheadcafe.com forums:

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
textBox3.Text = "ı"; // "Cross-thread operation not valid" here
}

The port_DataReceived event handler is being called on a secondary thread, but the user is attempting to update a control on the Main UI thread from it, and you cannot do that.

The Windows OS uses a single-threaded, message-processing based user interface, and any alternate thread interaction needs to be marshaled through the Windows message pump. Normally this involves checking a component's InvokeRequired property to determine if marshaling is necessary. You can call Invoke() directly and it will check this anyway, but it is more efficient to do it explicitly beforehand. Here is a complete example (minus of course, the progress bar and button you would need to drop onto the actual Form) that illustrates how this pattern might be used:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading;

 

namespace WFInvoke

{

    public partial class Form1 : Form

    {     

        public Form1()

        {

            InitializeComponent();

        }

 

        [STAThread]

        static void Main()

        {

            Application.Run(new Form1());

        }

 

        private  void IncrementMe()

        {

            for (int i = 0; i < 100; i++)

            {

                UpdateProgress();

                Thread.Sleep(100);

            }

            if(InvokeRequired)

            {

                Invoke(new MethodInvoker(Close));

            }

            else

            {

                Close();

            }

        }

 

        private  void UpdateProgress()

        {

            if (this.progressBar1.InvokeRequired)

            {

                MethodInvoker updateProgress = UpdateProgress;

                progressBar1.Invoke(updateProgress);

            }

            else

            {

                progressBar1.Increment(1);

            }

        }

 

        private void button1_Click(object sender, EventArgs e)

        {

            ThreadStart threadStart = IncrementMe;

            threadStart.BeginInvoke(null, null);

        }       

    }

}

We are running the IncrementMe method on a secondary thread, and we use the Invoke pattern to safely update the progress bar on the Main UI thread from inside the method.

BackgroundWorker Pattern

In .NET 1.1 it was necessary to wire up a lot of delegate-invoking to do something that turns out to be pretty common in Winforms development - to be able to be notified when a background thread method is complete, and also to be able to be notified periodically on its status. Being able to cancel a long-running background thread task is also desirable. This set of tasks is so common that the developers of the 2.0 Framework added the BackgroundWorker class, which makes all this much easier to do.

BackgroundWorker basically works like this:

1) You attach your long-running method to the BackgroundWorker's DoWork event.
2) To wire up progress notifications, you hook up an event listener to the BackgroundWorker class's ProgressChanged event, and set it's WorkerReportsProgress property to true.
3) You register your "Complete" method with the BackgroundWorker's RunWorkerCompleted event.
4) You can set the WorkerSupportsCancellation property to true, and a call to the BackgroundWorker.CancelAsync method will set the DoWorkEventArgs.CancellationPending flag, which you can then check and act accordingly in your long-running method that has been supplied to the BackgroundWorker class.
5) To start your method, you call the BackgroundWorker.RunWorkerAsync() method, passing a state parameter that is sent into your long-running method.

The only major drawback to using this substantially easier way to hook up all this eventing and reporting is that you cannot just use it with "any method" -- your method needs to conform to the System.ComponentModel.DoWorkEventHandler delegate, which requires arguments of type Object, and DoWorkEventArgs. Otherwise, a wrapper method will be required. In the example below, I illustrate the passing of multiple parameters by sending in an object[] array containing different types as the state parameter.

Aside from the above, you only need to remember to check EventArgs.Error inside the RunWorkerCompleted callback for any exception, otherwise the exception will be unreported; it doesn't even propagate to the AppDomain's UnhandledException event. Here is a somewhat trivial example, but it illustrates all the features described:

using System;

using System.Threading;

using System.ComponentModel;

 

class Program

{

    static BackgroundWorker bwkr;

    static void Main()

    {

        bwkr = new BackgroundWorker();

        bwkr.WorkerReportsProgress = true;

        bwkr.WorkerSupportsCancellation = true;

        bwkr.DoWork += bwkr_DoWork;

        bwkr.ProgressChanged += bwkr_ProgressChanged;

        bwkr.RunWorkerCompleted += bwkr_RunWorkerCompleted;

        object[] parms = {"one",2,DateTime.Now};

        bwkr.RunWorkerAsync(parms);

        Console.WriteLine("Press Enter during run to cancel.");

        Console.ReadLine();

        if (bwkr.IsBusy) bwkr.CancelAsync();

        Console.ReadLine();

    }

 

    static void bwkr_DoWork(object sender, DoWorkEventArgs e)

    {

        object[] o = (object[])e.Argument;

        foreach (object ob in o)

            Console.WriteLine("arg: " + ob.ToString());

        Console.WriteLine("==================================");

        int tot = 0;

        for (int i = 0; i <= 100; i += 10)

        {

            tot += i;

            if (bwkr.CancellationPending)

            {

                e.Cancel = true;

                return;

            }

            bwkr.ReportProgress(i);

            Thread.Sleep(1000);

        }

        e.Result = tot;    // This gets passed to RunWorkerCompleted

    }

 

    static void bwkr_RunWorkerCompleted(object sender,

    RunWorkerCompletedEventArgs e)

    {

        if (e.Cancelled)

            Console.WriteLine("Canceled by User.");

        else if (e.Error != null)

            Console.WriteLine("Worker exception: " + e.Error.ToString());

        else

            Console.WriteLine("Complete - " + e.Result);      // from DoWork

    }

 

    static void bwkr_ProgressChanged(object sender,

    ProgressChangedEventArgs e)

    {

        Console.WriteLine("bwkr: " + e.ProgressPercentage + "%");

    }

}

You can download this sample Visual Studio 2005 Solution containing both working examples.

Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Pete Tweets at peterbromberg


Didn't Find The Answer You Were Looking For?

EggHeadCafe has experts online right now that may know the answer to your question.  We pay them a bonus for answering as many questions as they can.  So, why not help them and yourself by becoming a member (free) and ask them your question right now?
Ask Question In Live Forum

Article Discussion: Tools for Updating Windows Forms UI from Background Threads
Peter Bromberg posted at Saturday, January 06, 2007 7:50 PM
Original Article
 

Great article
Atreyu Green replied to Peter Bromberg at Tuesday, January 20, 2009 8:00 AM

Very Useful your article.

Thank for your exp.

 




  $1000    Adam Houldsworth - $173  |  Jonathan VH - $154  |  Kirtan Patel - $116  |  Mr. Khan - $99  |  F Cali - $94  |  more Neado  |  Free Icons  |  Privacy  |   (c) 2010