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.
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:
Now click on show button.
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.
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
.
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
Click the show button
After two seconds: