Thread-Safe Calls With BackgroundWorker Class in C#

Introduction

Programming Windows Forms user interfaces is quite straightforward as long as you do not use multiple threads. But whenever your application has some actual work to do, it it becomes necessary to use threading to ensure the responsiveness of the UI. This is where Windows Forms programming can get quite complex.

The problem

Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.

As you know, Windows Forms is not thread safe in general. For example, it is not safe to get or set a property on a Windows.Forms control from any thread except the thread that handles the message queue. It is absolutely essential that you only make modifications to your Windows Forms controls from the message queue thread.

A brief example

A simple example of using this is to create a new form, add a textbox and a button to it.  Call the textbox myTextBox.

img1.gif

At the top of the code file add another using statement for the threading library.

using System.Threading;

In the button's Click event place the following code.

Thread th = new Thread(new ThreadStart(this.ThreadProcUnsafe));
th.Start();
myTextBox.Text = "Written by the main thread.";

Now add this code to the form:

private void ThreadProcUnsafe()
{
    Thread.Sleep(2000);
    this.textBox1.Text = "Written unsafely by the background thread.";
}

The complete form1.cs looks like as the following:

using System;
using System.Threading;
using System.Windows.Forms;
namespace ThreadUnsafecalltowindowform
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void UnsafeBtn_Click(object sender, EventArgs e)
        {
           Thread th = new Thread(new ThreadStart(this.ThreadProcUnsafe));
            th.Start();
            myTextBox1.Text = "Written by the main thread.";
        }
        private void ThreadProcUnsafe()
        {
            Thread.Sleep(2000);
            this.myTextBox1.Text = "Written unsafely by the background thread.";
        }
    }
}

Output

The output of this program looks like as the following:

img1.gif

Now click on show button.

img3.gif

After two seconds:

img4.gif

After two seconds the debugger raises an InvalidOperationException with the message, "Control control name accessed from a thread other than the thread it was created on." like as.

The Standard solution of the problem

We can resolve this problem via two ways

  1. Via InvokeRequired property- Each Windows Forms control has the InvokeRequired property which returns false if the current thread is the message queue thread. And there is the Invoke method which makes it possible to enqueue a delegate complete with parameters into the message queue of the control. For the discription of the InvokeRequired property Read my previous article "Thread safe calls using Windows Form Controls in C#" follow this link https://www.c-sharpcorner.com/UploadFile/1d42da/thread-safe-calls-using-windows-form-controls-in-C-Sharp/"

  2. Via BackgroundWorker - The preferred way to implement multithreading in your application is to use the BackgroundWorker component. The BackgroundWorker component uses an event-driven model for multithreading. The background thread runs your DoWork event handler, and the thread that creates your controls runs your ProgressChanged and RunWorkerCompleted event handlers. You can call your controls from your ProgressChanged and RunWorkerCompleted event handlers.

To make thread-safe calls by using BackgroundWorker

  1. Create a method to do the work that you want done in the background thread. Do not call controls created by the main thread in this method.
  2. Create a method to report the results of your background work after it finishes. You can call controls created by the main thread in this method.
  3. Bind the method created in step 1 to the DoWork event of an instance of BackgroundWorker, and bind the method created in step 2 to the same instance's RunWorkerCompleted event.
  4. To start the background thread, call the RunWorkerAsync() method of the  BackgroundWorker instance.

In the following code example, the DoWork event handler uses Sleep to simulate work that takes some time. It does not call the form's TextBox control. The TextBox control's Text property is set directly in the RunWorkerCompleted event handler.

code

A simple example of using this is to create a new form, add a textbox and a button to it.  Call the textbox myTextBox.

img1.gif

At the top of the code file add another using statement for the threading library.

using System.Threading;

In the button's Click event place the following code.

backgroundWorker1 = new BackgroundWorker();
this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); 
this.backgroundWorker1.RunWorkerAsync();
mytextBox.Text = "Written by the main thread.";

Now add this code to the form.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(2000);
}
private void backgroundWorker1_RunWorkerCompleted(  object sender, RunWorkerCompletedEventArgs e) 
{
    this.mytextBox.Text ="# Written by the main thread after the background thread completed.";
}

Outside of the form's class add this declaration.

private BackgroundWorker backgroundWorker1=null;

The complete form1.cs file are look like as.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace ThreadSafeCallsbyusingBackgroundWorker
{
    public partial class Form1 : Form
    {
        private BackgroundWorker backgroundWorker1=null;
        public Form1()
        {
            InitializeComponent();
        }
        private void Backgroundworkerbutton1_Click(object sender, EventArgs e)
        {
            backgroundWorker1 = new BackgroundWorker();
            this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
            this.backgroundWorker1.RunWorkerCompleted += new          System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
            this.backgroundWorker1.RunWorkerAsync();
            mytextBox.Text = "Written by the main thread.";
        }
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(2000);
        }
        private void backgroundWorker1_RunWorkerCompleted(  object sender, RunWorkerCompletedEventArgs e)        {
          this.mytextBox.Text ="# Written by the main thread after the background thread completed.";
        }
    }
}

Output

Run the application

img1.gif

Click on show button

img6.gif

After two second

img7.gif


Similar Articles