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
.
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:
Now click on show button.
After two seconds:
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
-
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 " http://www.c-sharpcorner.com/UploadFile/1d42da/thread-safe-calls-using-windows-form-controls-in-C-Sharp/ "
-
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
- 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.
- 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.
- 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.
- 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
.
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
Click on show button
After two second