According to the MSDN Documentation for ThreadState, the Stopped state can be entered by one of two ways: the thread exiting, or the thread being aborted.
Is there some mechanism for telling whether a thread has entered the Stopped state by exiting normally? Thanks!
A thread can reach the Stopped state in several ways:
I don't know if you are looking to differentiate between all three states, but if all you are really interested in is whether the thread completed successfully, I would suggest using a shared data structure of some kind (a synchronized dictionary would work) that the main loop of a thread updates as it terminates. You can use the ThreadName property as the key in this shared dictionary. Other threads that are interested in termination state, could read from this dictionary to determine the final status of the thread.
After looking a bit more at the MSDN documentation, you should be able to differentiate an externally aborted thread using the ThreadState
property. This should be set to ThreadState.Aborted
when a thread responds to the Abort() call. However, unless you have control of the thread code that is run, I don't think that you can differentiate between a thread that just exited its main method, vs one that terminated with an exception.
Keep in mind however, if you control the code that starts the thread, you can always substitute your own method that internally calls the code that runs the main thread logic and inject exception detection (as I described above) there.
Is the thread that you want to monitor your code? If so, you could wrap the whole thing in a class, and either raise an event when you finish or use a WaitHandle (I would use a ManualResetEvent) to signal completion. -- completely encapsulate the background logic. You can also use this encapsulation to capture an exception and then raise an event.
Something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BackgroundWorker
{
public class BackgroundWorker
{
///
/// Raised when the task completes (you could enhance this event to return state in the event args)
///
public event EventHandler TaskCompleted;
///
/// Raised if an unhandled exception is thrown by the background worker
///
public event EventHandler BackgroundError;
private ThreadStart BackgroundTask;
private readonly ManualResetEvent WaitEvent = new ManualResetEvent(false);
///
/// ThreadStart is the delegate that you want to run on your background thread.
///
///
public BackgroundWorker(ThreadStart backgroundTask)
{
this.BackgroundTask = backgroundTask;
}
private Thread BackgroundThread;
///
/// Starts the background task
///
public void Start()
{
this.BackgroundThread = new Thread(this.ThreadTask);
this.BackgroundThread.Start();
}
private void ThreadTask()
{
// the task that actually runs on the thread
try
{
this.BackgroundTask();
// completed only fires on successful completion
this.OnTaskCompleted();
}
catch (Exception e)
{
this.OnError(e);
}
finally
{
// signal thread exit (unblock the wait method)
this.WaitEvent.Set();
}
}
private void OnTaskCompleted()
{
if (this.TaskCompleted != null)
this.TaskCompleted(this, EventArgs.Empty);
}
private void OnError(Exception e)
{
if (this.BackgroundError != null)
this.BackgroundError(this, new BackgroundWorkerErrorEventArgs(e));
}
///
/// Blocks until the task either completes or errrors out
/// returns false if the wait timed out.
///
/// Timeout in milliseconds, -1 for infinite
///
public bool Wait(int timeout)
{
return this.WaitEvent.WaitOne(timeout);
}
}
public class BackgroundWorkerErrorEventArgs : System.EventArgs
{
public BackgroundWorkerErrorEventArgs(Exception error) { this.Error = error; }
public Exception Error;
}
}
Note: you need some code to keep Start from being called twice, and you might want to add a state property for passing state to your thread.
Here's a console app that demonstrates the use of this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BackgroundWorker
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Test 1");
BackgroundWorker worker = new BackgroundWorker(BackgroundWork);
worker.TaskCompleted += new EventHandler(worker_TaskCompleted);
worker.BackgroundError += new EventHandler(worker_BackgroundError);
worker.Start();
worker.Wait(-1);
Console.WriteLine("Test 2");
Console.WriteLine();
// error case
worker = new BackgroundWorker(BackgroundWorkWithError);
worker.TaskCompleted += new EventHandler(worker_TaskCompleted);
worker.BackgroundError += new EventHandler(worker_BackgroundError);
worker.Start();
worker.Wait(-1);
Console.ReadLine();
}
static void worker_BackgroundError(object sender, BackgroundWorkerErrorEventArgs e)
{
Console.WriteLine("Exception: " + e.Error.Message);
}
private static void BackgroundWorkWithError()
{
throw new Exception("Foo");
}
static void worker_TaskCompleted(object sender, EventArgs e)
{
Console.WriteLine("Completed");
}
private static void BackgroundWork()
{
Console.WriteLine("Hello!");
}
}
}
You may want to look at the BackgroundWorker class. It has a generic event handler for when the thread completes. In there you can check if the thread completed due to an error, because it was canceled or because it finished successfully.
Assuming the main thread needs to wait for the worker thread to complete successfully, I usually use ManualResetEvent. Or, for multiple worker threads, the new CountDownEvent from Parallels Extensions, like so.
I use a CancellationTokenSource
to ask the thread to exit gracefully.
I then use var exitedProperly = _thread.Join(TimeSpan.FromSeconds(10);
which waits for the thread to exit.
If exitedProperly==false
, I push an error into the log.
I use this pattern mainly when I am in the Dispose()
function, and I am trying to clean up any threads I have created.
A thread can only be aborted by calling Thread.Abort(), which results in a ThreadAbortException, so through exception handling you should be able to determine a normal exit vs an aborted exit.