views:

516

answers:

5

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: 

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.

Joseph
As far as I know, a ThreadAbortException is only raised in the thread that is actually aborted, not in the calling code. Because of that, I can't catch that exception in the calling code.
Pwninstein
@Pwninstein That is correct. I was assuming you had control over the thread code. If you only have access to the calling code then my answer isn't going to really be of any use to you.
Joseph
+1  A: 

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.

Jacob Adams
Funny you should mention that... I'm actually implementing a different flavor of the BackgroundWorker class - one where you can cancel a long running operation. Thanks for the suggestion, though!
Pwninstein
My understanding, is that threads that run BackgroundWorker instances don't actually terminate when the worker completes. You can determine when the worker finishes it's work, but that's different from when the thread terminates. Now, given the question, this may be a workable technique to determine that status of a work task ... but it's worth pointing out the difference.
LBushkin
You can cancel a long running process with the default background worker thread. Just call CancelAsync. In the worker thread, you then have to look for at the CancellationPending flag
Jacob Adams
That won't work in the case I'm interested in - a background thread completing a potentially long-running network operation (i.e. retrieving a list of SQL servers). From what I understand, CancelAsync sets a flag for you to handle manually in your processing code, which doesn't help if your processing code is a single blocking call. Believe me, that's the first thing I looked at. :) Thanks!
Pwninstein
+2  A: 

A thread can reach the Stopped state in several ways:

  • It's main method can exit without any errors.
  • An uncaught exception on the thread can terminate it.
  • Another thread could call Thread.Abort(), which will cause a ThreadAbortException to be thrown on that thread.

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.

LBushkin
+1  A: 

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.

zvolkov
+1  A: 

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!");
        }
    }
}


JMarsch