Sunday, June 28, 2009

C# BackgroundWorker

From time to time, we all create GUIs for our applications. One of the most annoying things about a GUI is when it locks up, or gives the appearance of locking up. The quickest solution for this problem is Application.DoEvents().

That's fine for something that is looping quickly, but there is a better solution. It's easy to fix this problem with the C# BackgroundWorker Class. What it does is create another thread for you to do long running tasks on that would slow down your GUI.

In Visual Studio, create a new C# Windows Forms project. On the form, select from the Toolbox, under the Components Section, the BackgroundWorker object and drop it on the form. In the grey area below the form, you will see backgroundWorker1. Double click it to create the DoWork() method. This is where you will put your long running task.

Now, when you need to update, or touch, any UI component like a ProgressBar or TextBox, you cannot do that from a BackgroudWorker thread. Let's say we need to append a line of text to a textbox from our background thread. Let's create a method to do this:


private void AppendTextToDebugWindow(string textToAppend)
        {
            textBox1.AppendText(textToAppend + Environment.NewLine);
        }
Now, if you call this from your backgroundworker, you will get an InvalidOperationException - Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

This is pretty easy to fix. Create a delegate with the same signature as the above method. In our case:
delegate void AppendTextToDebugWindowHandler(string textToAppend);
Then, in the AppendTextToDebugWindow, change it to look like this:

private void AppendTextToDebugWindow(string textToAppend)
        {
            if (textBox1.InvokeRequired)
            {
                this.Invoke(new AppendTextToDebugWindowHandler(this.AppendTextToDebugWindow), new object[] { textToAppend });
                return;
            }
            textBox1.AppendText(textToAppend + Environment.NewLine);
        }
See! I told you this was easy! Here's is the sample Form1 that I used:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace TestBackgroundWorker
{
    delegate void AppendTextToDebugWindowHandler(string textToAppend);

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // Don't forget to check if the background worker is busy
            if (!backgroundWorker1.IsBusy)
            {
                backgroundWorker1.RunWorkerAsync();
                return;
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            AppendTextToDebugWindow("backgroundWorker1 started");
            Thread.Sleep(5000);
            AppendTextToDebugWindow("backgroundWorker1 done");
        }

        private void AppendTextToDebugWindow(string textToAppend)
        {
            if (textBox1.InvokeRequired)
            {
                this.Invoke(new AppendTextToDebugWindowHandler(this.AppendTextToDebugWindow), new object[] { textToAppend });
                return;
            }
            textBox1.AppendText(textToAppend + Environment.NewLine);
        }
    }
}

2 comments:

  1. +1 Thank you! Just what I needed to understand the cross-threading problem.

    ReplyDelete