Updating UI Elements from a Background Thread – Part 2

In the previous post, I showed you the possible issue associated with updating UI Elements from the background thread. In this post, I will show you two easy ways to fix the problem.

The first method is to use the delegate function and the UI elements’ Invoke method. In C#, a delegate is very similar to a function pointer in C and C++, and it encapsulates a reference to a method inside a delegate object. This delegate object can be passed to code that calls the reference method. Unlike function pointer in C or C++, a delegate is object-oriented, type-safe, and more secure.

There are several types of the Invoke methods in .NET. I list them here for your reference:

Delegate.Invoke is used to execute a delagate on the current thread.

Delegate.BeginInvoke is used to execute a delegate on a separate thread. This means you can start an operation which won’t block your current thread and it will be executed on its own thread.

Control.Invoke is used to executed a delegate on the UI thread of that element. If you have a delegate which updates the user interface, you can call this method from the other thread to execute the update operation on the UI thread.

Control.BeginInvoke is similar to the above method, but it does in an asynchronous way. This means while Control.Invoke waits until the UI thread has finished executing the delegate, Control.BinginInvoke returns immediately.

Here I’ll show you how to use the delegate and UI elements’ Invoke method to fix the problem in my previous post. Start with a new Winform project and use the same layout of the form1 as in Part 1. The code is almost identical to Part 1, except for adding a delegate function, a updating UI element method, and a UI element’s Invoke method, as shown in red in the following code list:


using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace BkgroundWorkerTest01
{
    public partial class Form1 : Form
    {
        private delegate void UpdateDelegate(double x);
        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerSupportsCancellation = true;
            backgroundWorker1.DoWork += new DoWorkEventHandler(
                    backgroundWorker1_DoWork);
            backgroundWorker1.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(
                backgroundWorker1_RunWorkerCompleted);
         }

        void backgroundWorker1_RunWorkerCompleted(object sender,
                RunWorkerCompletedEventArgs e)
        {
            if (!(e.Cancelled))
                label2.Text = "Calculation Completed!";
            else
                label2.Text = "Calculation Cancelled!";
        }

        void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 500; i++)
            {
                Thread.Sleep(100);
                if (backgroundWorker1.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                Double x = Math.Sin(1.0 * i / 10.0);
                UpdateDelegate update = new UpdateDelegate(UpdateUIElement);
                textBox1.Invoke(update, new object[] { x });
                //textBox1.Text = x.ToString();

            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            backgroundWorker1.CancelAsync();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }

        private void UpdateUIElement(double x)
        {
            textBox1.Text = x.ToString();
        }
    }
}

Now, running the program by pressing F5 and clicking on the “Start” button, you will find the textbox1 is updating the calculation results from the background thread.

In the following, I’ll discuss another method for updating UL elements from the background thread. The BackgroundWorker has a special method called ReportProgress. If you need the background computations to report on its progress, you can call this method to raise the ProgressChanged event. Remember that in order to use this method, you must set the WorkerReportsProgress property value to true, otherwise ReportProgress will throw an InvalidOperationException.

Using the same form1 layout. The following code shows you how to update UI elements using the ReportProgress method:


using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace BkgroundWorkerTest01
{
    public partial class Form1 : Form
    {
      //private delegate void UpdateDelegate(double x); 

        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerSupportsCancellation = true;
            backgroundWorker1.DoWork += new DoWorkEventHandler(
                 backgroundWorker1_DoWork);
            backgroundWorker1.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(
                backgroundWorker1_RunWorkerCompleted);
           backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.ProgressChanged +=
                new ProgressChangedEventHandler(
                backgroundWorker1_ProgressChanged);
        }

        void backgroundWorker1_ProgressChanged(object sender,
                          ProgressChangedEventArgs e)
        {
          UpdateUIElement(Convert.ToDouble(e.UserState));
        }

        void backgroundWorker1_RunWorkerCompleted(object sender,
               RunWorkerCompletedEventArgs e)
        {
            if (!(e.Cancelled))
                label2.Text = "Calculation Completed!";
            else
                label2.Text = "Calculation Cancelled!";
        }

        void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 500; i++)
            {
                Thread.Sleep(100);
                if (backgroundWorker1.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                Double x = Math.Sin(1.0 * i / 10.0);
                backgroundWorker1.ReportProgress(0, x);
                //UpdateDelegate update = new UpdateDelegate(UpdateUIElement);
                //textBox1.Invoke(update, new object[] { x });

                //textBox1.Text = x.ToString();

            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            backgroundWorker1.CancelAsync();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }

        private void UpdateUIElement(double x)
        {
            textBox1.Text = x.ToString();
        }
    }
} 

The red part in the above code shows the difference between the Invoke and ReportProgress methods. Now, running this program again, you will see the textBox1 is updating the results from the background thread. Here we use the UserState to report the progress.

You may notice that both the above examples only update a single UI element, textBox1, from the background thread. I also got several emails asking how to update multiple UI elements from a background thread. This will be the topic of my next post.

This entry was posted in .NET and CSharp. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>