Thread-Safe Calls Using Windows Form Controls 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 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 is as follows:

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 is as follows:

img2.gif

Now click on show button.

img3.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.

img4.gif

If you are using ActiveX controls on a form, you may receive the cross-thread InvalidOperationException when you run under the debugger. When this occurs, the ActiveX control does not support multithreading.

The Standard solution of the problem

There is, of course, a mechanism to deal with this. 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.

Since the delegate is called directly from the message queue, no threading issues arise. But this style of programming can be quite tedious. Just to do something as simple as setting a text property or enabling/disabling a control, you have to define a separate method with a matching delegate.

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.

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 ThreadProcSafe()
{
    Thread.Sleep(2000); 
    string text = "Written by the background thread.";
    if (this.myTextBox.InvokeRequired)
    {
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text + " (Invoke)" });
    }
    else
    {
        this.myTextBox.Text = text + " (No Invoke)";
    }
}

private void SetText(string text)
{
    this .myTextBox.Text=text;
}

Outside of the form's class add this declaration.

delegate void SetTextCallback(string text);

The complete form1.cs file are look like as

using System;
using System.Windows.Forms;
using System.Threading;
namespace ThreadSafeCallsToWindowForm
{
    delegate void SetTextCallback(string text);
    public partial class Form1 : Form
    { 
        public Form1()
        {
            InitializeComponent();
       }      
        private void SetText(string text)
        {
            this .myTextBox.Text=text;
        }
        private void Safebutton_Click(object sender, EventArgs e)
        {
            Thread th = new Thread(new ThreadStart(this.ThreadProcSafe));
            th.Start();
            myTextBox.Text = "Written by the main thread.";
        }
        private void ThreadProcSafe()
        {
           Thread.Sleep(2000);
             string text = "Written by the background thread.";
            if (this.myTextBox.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text + " (Invoke)" });
           }
            else
            {
                // It's on the same thread, no need for Invoke
                this.myTextBox.Text = text + " (No Invoke)";
            }
        } 
    }
}

Output

Run the application

img2.gif

Click the show button

img3.gif

After two seconds:

img5.gif


Similar Articles