Compact Framework, Windows Mobile 6, C#.
I'm having a go at some background threading on the compact framework, and have a question re: terminating the worker thread.
The Code
I have the following ThreadWorker class (code from here), which when executed, will perform a check at certain points to see if it should quit or not.....
public class ThreadWorker
{
public event EventHandler<ProgressEventArgs> OnProgress;
protected virtual void Progress(ProgressEventArgs args)
{
if (OnProgress != null)
OnProgress(this, args);
}
private void DoLongProcess()
{
// This will take a long time.
Thread.Sleep(15000);
Progress(new ProgressEventArgs("Some info for the UI to display."));
Thread.Sleep(15000);
}
public void DoSomeBackgroundWork()
{
try
{
while (!Stopping)
{
DoLongProcess();
if (Stopping) return;
DoLongProcess();
if (Stopping) return;
DoLongProcess();
if (Stopping) return;
DoLongProcess();
if (Stopping) return;
}
}
finally
{
SetStopped();
}
Console.WriteLine("DoSomeBackgroundWork: Terminating gracefully.");
}
/// <summary>
/// Lock covering stopping and stopped
/// </summary>
readonly object locker = new object();
/// <summary>
/// Whether or not the worker thread has been asked to stop
/// </summary>
bool stopping = false;
/// <summary>
/// Whether or not the worker thread has stopped
/// </summary>
bool stopped = false;
/// <summary>
/// Returns whether the worker thread has been asked to stop.
/// This continues to return true even after the thread has stopped.
/// </summary>
public bool Stopping
{
get
{
lock (locker)
{
return stopping;
}
}
}
/// <summary>
/// Returns whether the worker thread has stopped.
/// </summary>
public bool Stopped
{
get
{
lock (locker)
{
return stopped;
}
}
}
/// <summary>
/// Tells the worker thread to stop, typically after completing its
/// current work item. (The thread is *not* guaranteed to have stopped
/// by the time this method returns.)
/// </summary>
public void Stop()
{
lock (locker)
{
stopping = true;
}
}
/// <summary>
/// Called by the worker thread to indicate when it has stopped.
/// </summary>
void SetStopped()
{
lock (locker)
{
stopped = true;
}
}
}
...and the following form initiates the thread...
public partial class Test : Form
{
public Test()
{
InitializeComponent();
}
private ThreadWorker myThreadWorker;
private Thread t = null;
private void Test_Load(object sender, EventArgs e)
{
myThreadWorker = new ThreadWorker();
myThreadWorker.OnProgress += new EventHandler<ProgressEventArgs>(myThreadWorker_OnProgress);
}
private void miStart_Click(object sender, EventArgs e)
{
try
{
listResults.Items.Insert(0, "Set-up Thread.");
t = new Thread(myThreadWorker.DoSomeBackgroundWork);
t.Name = "My Thread";
t.Priority = ThreadPriority.BelowNormal;
t.Start();
listResults.Items.Insert(0, "Thread started.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void miStop_Click(object sender, EventArgs e)
{
try
{
listResults.Items.Insert(0, "Waiting for My Thread to terminate.");
listResults.Refresh();
myThreadWorker.Stop();
t.Join();
listResults.Items.Insert(0, "My Thread Finished.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private delegate void InsertToListBoxDelegate(String text);
private void InsertToListBox(String text)
{
if (InvokeRequired)
Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text });
else
{
listResults.Items.Insert(0, "{0}".With(text));
listResults.Refresh();
}
}
void myThreadWorker_OnProgress(object sender, ProgressEventArgs e)
{
InsertToListBox(e.Text);
}
}
The Problem
When I click on the Stop button it calls...
myThreadWorker.Stop();
t.Join();
listResults.Items.Insert(0, "My Thread Finished.");
...what I was expecting is for the ThreadWorker to carry on with its current DoLongProcess() until it had finished, and still raise event to the UI via the OnProgress event handler and myThreadWorker_OnProgress.
However, what actually happens is when OnProgress is raised, the application freezes on the line reading...
Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text });
The question
How do I call...
myThreadWorker.Stop();
t.Join();
...and still respond to events from the background thread until it is terminated?
By calling Thread.Join you have blocked the UI thread. By calling Control.Invoke you have blocked the worker thread. Invoke posts a message to the UI thread's message queue and waits for it to be processed. But, since the UI thread is blocked waiting for the worker thread to complete it cannot begin executing the delegate which turn forces the worker thread to stall. The threads are now deadlocked.
The biggest problem is the Join call. It would be better to avoid calling Join from the UI thread. Instead, disable the stop button after it is clicked to provide feedback to the user that the stop request was accepted and is pending. You might even want to display a simple message on the status bar stating as much to make it clear. Then when the last OnProgress event is raised that will be your signal that the thread terminated and you can reset everything on the form.
However, you might want to consider a radical shift in thought. I think the Control.Invoke methodology is way overused. Instead of using Control.Invoke to marshal the execution of the event handler back onto the UI thread you could have the UI thread poll for the progress information using a timer. When new progress information is available the worker will publish it to some variable or data structure. This has several advantages.
Control.Invoke imposes.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With