Introduction
The .NET framework provides a lot of ways to implement multithreading programs. I want
to show how we can run a worker thread which makes syncronous calls to a user
interface (for example, a thread that reads a long recordset and fills some control
in the form).
To run thread I use:
- Thread instance and main thread function
- Two events used to stop thread. First event is set when main thread wants
to stop worker thread; second event is set by worker thread when it really
stops.
.NET allows you to call System.Windows.Forms.Control functions only from the thread
in which the control was created. To run them from another thread we need to use the
Control.Invoke (synchronous call) or Control.BeginInvoke (asynchronous call)
functions. For tasks like showing database records we need Invoke.
To implement this we will use:
- A Delegate type for calling the form function. Delegate instance and function called using this delegate
- The
Invoke call from the worker thread.
The next problem is to stop the worker thread correctly. The steps to do this are:
- Set the event "Stop Thread"
- Wait for the event "Thread is stopped"
- Wait for the event process messages using the
Application.DoEvents
function. This prevents deadlocks because the worker thread makes Invoke calls
which are processed in the main thread.
The thread function checks every iteration whether the "Stop Thread" event has been set.
If the event is set the function invokes clean-up operations, sets the event "Thread is
stopped" and returns.
Demo project has two classes: MainForm and LongProcess. The LongProcess.Run
function runs in a thread and fills the list box with some lines. The worker thread
may finish naturally or may be stopped when user presses the "Stop Thread"
button or closes the form.
Code fragments
namespace WorkerThread
{
public delegate void DelegateAddString(String s);
public delegate void DelegateThreadFinished();
public class MainForm : System.Windows.Forms.Form
{
Thread m_WorkerThread;
ManualResetEvent m_EventStopThread;
ManualResetEvent m_EventThreadStopped;
public DelegateAddString m_DelegateAddString;
public DelegateThreadFinished m_DelegateThreadFinished;
public MainForm()
{
InitializeComponent();
m_DelegateAddString = new DelegateAddString(this.AddString);
m_DelegateThreadFinished = new DelegateThreadFinished(this.ThreadFinished);
m_EventStopThread = new ManualResetEvent(false);
m_EventThreadStopped = new ManualResetEvent(false);
}
private void btnStartThread_Click(object sender, System.EventArgs e)
{
m_EventStopThread.Reset();
m_EventThreadStopped.Reset();
m_WorkerThread = new Thread(new ThreadStart(this.WorkerThreadFunction));
m_WorkerThread.Name = "Worker Thread Sample";
m_WorkerThread.Start();
}
private void WorkerThreadFunction()
{
LongProcess longProcess;
longProcess = new LongProcess(m_EventStopThread, m_EventThreadStopped, this);
longProcess.Run();
}
private void StopThread()
{
if ( m_WorkerThread != null && m_WorkerThread.IsAlive ) {
m_EventStopThread.Set();
while (m_WorkerThread.IsAlive)
{
if ( WaitHandle.WaitAll(
(new ManualResetEvent[] {m_EventThreadStopped}),
100,
true) )
{
break;
}
Application.DoEvents();
}
}
}
private void AddString(String s)
{
listBox1.Items.Add(s);
}
private void ThreadFinished()
{
btnStartThread.Enabled = true;
btnStopThread.Enabled = false;
}
}
}
namespace WorkerThread
{
public class LongProcess
{
public void Run()
{
int i;
String s;
for (i = 1; i <= 10; i++)
{
s = "Step number " + i.ToString() + " executed";
Thread.Sleep(400);
m_form.Invoke(m_form.m_DelegateAddString, new Object[] {s});
if ( m_EventStop.WaitOne(0, true) )
{
m_EventStopped.Set();
return;
}
}
m_form.Invoke(m_form.m_DelegateThreadFinished, null);
}
}
}