Click here to Skip to main content
15,896,348 members
Articles / Programming Languages / C#

SafeThread - Prevent Unhandled Exceptions in Secondary Threads

Rate me:
Please Sign up or sign in to vote.
4.76/5 (33 votes)
19 Aug 2008CPOL6 min read 98.7K   2.2K   72  
Prevent unhandled exceptions in secondary threads, using SafeThread
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Reflection;

namespace nov8r.CodeProject.SafeThreadDemo
{
    /// <summary>
    /// simple no-arg delegate type; can use this for anonymous methods, e.g.
    /// <code>
    ///     SafeThread safeThrd = new SafeThread((SimpleDelegate) delegate { dosomething(); });
    /// </code>
    /// </summary>
    public delegate void SimpleDelegate();

    /// <summary>
    /// delegate for thread-threw-exception event
    /// </summary>
    /// <param name="thrd">the SafeThread that threw the exception</param>
    /// <param name="ex">the exception throws</param>
    public delegate void ThreadThrewExceptionHandler(SafeThread thrd, Exception ex);

    /// <summary>
    /// delegate for thread-completed event
    /// </summary>
    /// <param name="thrd">the SafeThread that completed processing</param>
    /// <param name="hadException">true if the thread terminated due to an exception</param>
    /// <param name="ex">the exception that terminated the thread, or null if completed successfully</param>
    public delegate void ThreadCompletedHandler(SafeThread thrd, bool hadException, Exception ex);

    /// <summary>
    /// This class implements a Thread wrapper to trap unhandled exceptions 
    /// thrown by the thread-start delegate. Add ThreadException event 
    /// handlers to be notified of such exceptions and take custom actions
    /// (such as restart, clean-up, et al, depending on what the SafeThread was
    /// doing in your application). Add ThreadCompleted event handlers to be
    /// notified when the thread has completed processing.
    /// </summary>
    public partial class SafeThread : MarshalByRefObject
    {
        /// <summary>
        /// internal thread
        /// </summary>
        protected Thread _thread;
        /// <summary>
        /// gets the internal thread being used
        /// </summary>
        public Thread ThreadObject
        {
            get { return _thread; }
        }

        /// <summary>
        /// the thread-start object, if any
        /// </summary>
        protected ThreadStart _Ts;
        /// <summary>
        /// the parameterized thread-start object, if any
        /// </summary>
        protected ParameterizedThreadStart _Pts;
        /// <summary>
        /// the SimpleDelegate target, if any
        /// </summary>
        protected SimpleDelegate dlg;

        /// <summary>
        /// the thread-start argument object, if any
        /// </summary>
        protected object _arg;
        /// <summary>
        /// gets the thread-start argument, if any
        /// </summary>
        public object ThreadStartArg
        {
            get { return _arg; }
        }
        /// <summary>
        /// the last exception thrown
        /// </summary>
        protected Exception _lastException;
        /// <summary>
        /// gets the last exception thrown
        /// </summary>
        public Exception LastException
        {
            get { return _lastException; }
        }
        /// <summary>
        /// the name of the internal thread
        /// </summary>
        private string _name;
        /// <summary>
        /// gets/sets the name of the internal thread
        /// </summary>
        public string Name
        {
            get 
            {
                if (_name == null) { return "SafeThread#" + this.GetHashCode().ToString(); }
                return _name; 
            }
            set { _name = value; }
        }

        private object _tag;
        /// <summary>
        /// object tag - use to hold extra info about the SafeThread
        /// </summary>
        public object Tag
        {
            get { return _tag; }
            set { _tag = value; }
        }

        /// <summary>
        /// default constructor for SafeThread
        /// </summary>
        public SafeThread()
            : base()
        {
        }

        /// <summary>
        /// SafeThread constructor using ThreadStart object
        /// </summary>
        /// <param name="ts">ThreadStart object to use</param>
        public SafeThread(ThreadStart ts)
            : this()
        {
            _Ts = ts;
            _thread = new Thread(ts);
        }

        /// <summary>
        /// SafeThread constructor using ParameterizedThreadStart object
        /// </summary>
        /// <param name="pts">ParameterizedThreadStart to use</param>
        public SafeThread(ParameterizedThreadStart pts)
            : this()
        {
            _Pts = pts;
            _thread = new Thread(pts);
        }

        /// <summary>
        /// SafeThread constructor using SimpleDelegate object for anonymous methods, e.g.
        /// <code>
        ///     SafeThread safeThrd = new SafeThread((SimpleDelegate) delegate { dosomething(); });
        /// </code>
        /// </summary>
        /// <param name="sd"></param>
        public SafeThread(SimpleDelegate sd)
            : this()
        {
            dlg = sd;
            _Pts = new ParameterizedThreadStart(this.CallDelegate);
            _thread = new Thread(_Pts);
        }

        /// <summary>
        /// thread-threw-exception event
        /// </summary>
        public event ThreadThrewExceptionHandler ThreadException;

        /// <summary>
        /// called when a thread throws an exception
        /// </summary>
        /// <param name="ex">Exception thrown</param>
        protected void OnThreadException(Exception ex)
        {
            try
            {
                if (ex is ThreadAbortException && !ShouldReportThreadAbort) 
                {
                    return; 
                }

                if (ThreadException != null)
                {
                    ThreadException.Invoke(this, ex);
                }
            }
            catch { }
        }

        /// <summary>
        /// thread-completed event
        /// </summary>
        public event ThreadCompletedHandler ThreadCompleted;

        /// <summary>
        /// called when a thread completes processing
        /// </summary>
        protected void OnThreadCompleted(bool bHadException, Exception ex)
        {
            try
            {
                if (ThreadCompleted != null)
                {
                    ThreadCompleted.Invoke(this, bHadException, ex);
                }
            }
            catch { }
        }

        /// <summary>
        /// starts thread with target if any
        /// </summary>
        protected void startTarget()
        {
            Exception exceptn = null;
            bool bHadException = false;
            try
            {
                bThreadIsAborting = false;
                if (_Ts != null)
                {
                    _Ts.Invoke();
                }
                else if (_Pts != null)
                {
                    _Pts.Invoke(_arg);
                }
            }
            catch (Exception ex)
            {
                bHadException = true;
                exceptn = ex;
                this._lastException = ex;
                OnThreadException(ex);
            }
            finally
            {
                OnThreadCompleted(bHadException, exceptn);
            }
        }

        /// <summary>
        /// thread-start internal method for SimpleDelegate target
        /// </summary>
        /// <param name="arg">unused</param>
        protected void CallDelegate(object arg)
        {
            this.dlg.Invoke();
        }

        /// <summary>
        /// starts thread execution
        /// </summary>
        public void Start()
        {
            _thread = new Thread(new ThreadStart(startTarget));
            _thread.Name = this.Name;
            if (_aptState != null) { _thread.TrySetApartmentState((ApartmentState)_aptState); }
            _thread.Start();
        }

        /// <summary>
        /// starts thread execution with parameter
        /// </summary>
        /// <param name="val">parameter object</param>
        public void Start(object val)
        {
            _arg = val;
            Start();
        }

        /// <summary>
        /// flag to control whether thread-abort exception is reported or not
        /// </summary>
        protected bool bReportThreadAbort = false;
        /// <summary>
        /// gets/sets a flag to control whether thread-abort exception is reported or not
        /// </summary>
        public bool ShouldReportThreadAbort
        {
            get { return bReportThreadAbort; }
            set { bReportThreadAbort = value; }
        }

        /// <summary>
        /// flag for when thread is aborting
        /// </summary>
        protected bool bThreadIsAborting = false;

        /// <summary>
        /// abort the thread execution
        /// </summary>
        public void Abort()
        {
            bThreadIsAborting = true;
            _thread.Abort();
        }

        /// <summary>
        /// gets or sets the Culture for the current thread.
        /// </summary>
        public System.Globalization.CultureInfo CurrentCulture
        {
            get { if (_thread != null) { return _thread.CurrentCulture; } return null; }
        }

        /// <summary>
        /// gets or sets the current culture used by the Resource Manager
        /// to look up culture-specific resources at run time.
        /// </summary>
        public System.Globalization.CultureInfo CurrentUICulture
        {
            get { if (_thread != null) { return _thread.CurrentUICulture; } return null; }
        }

        /// <summary>
        /// gets an System.Threading.ExecutionContext object that contains information
        /// about the various contexts of the current thread.
        /// </summary>
        public ExecutionContext ExecutionContext
        {
            get { if (_thread != null) { return _thread.ExecutionContext; } return null; }
        }

        /// <summary>
        /// Returns an System.Threading.ApartmentState value indicating the apparent state.
        /// </summary>
        /// <returns></returns>
        public ApartmentState GetApartmentState()
        {
            if (_thread != null) { return _thread.GetApartmentState(); }
            return ApartmentState.Unknown;
        }

        /// <summary>
        /// Interrupts a thread that is in the WaitSleepJoin thread state.
        /// </summary>
        public void Interrupt()
        {
            if (_thread != null) { _thread.Interrupt(); }
        }

        /// <summary>
        /// gets a value indicating the execution status of the thread
        /// </summary>
        public bool IsAlive
        {
            get { if (_thread != null) { return _thread.IsAlive; } return false; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether or not a thread is a background thread
        /// </summary>
        public bool IsBackground
        {
            get { if (_thread != null) { return _thread.IsBackground; } return false; }
            set { if (_thread != null) { _thread.IsBackground = value; } }
        }

        /// <summary>
        /// gets a value indicating whether or not a thread belongs to the managed thread pool
        /// </summary>
        public bool IsThreadPoolThread
        {
            get { if (_thread != null) { return _thread.IsThreadPoolThread; } return false; }
        }

        /// <summary>
        /// Blocks the calling thread until a thread terminates,
        /// while continuing to perform standard COM and SendMessage pumping.
        /// </summary>
        public void Join()
        {
            if (_thread != null) { _thread.Join(); }
        }

        /// <summary>
        /// Blocks the calling thread until a thread terminates or the specified time elapses,
        /// while continuing to perform standard COM and SendMessage pumping.
        /// </summary>
        /// <param name="millisecondsTimeout">the number of milliseconds to wait for the
        /// thread to terminate</param>
        public bool Join(int millisecondsTimeout)
        {
            if (_thread != null) { return _thread.Join(millisecondsTimeout); }
            return false;
        }

        /// <summary>
        /// Blocks the calling thread until a thread terminates or the specified time elapses,
        /// while continuing to perform standard COM and SendMessage pumping.
        /// </summary>
        /// <param name="timeout">a System.TimeSpan set to the amount of time to wait
        /// for the thread to terminate </param>
        public bool Join(TimeSpan timeout)
        {
            if (_thread != null) { return _thread.Join(timeout); }
            return false;
        }

        /// <summary>
        /// Gets a unique identifier for the current managed thread
        /// </summary>
        public int ManagedThreadId
        {
            get { if (_thread != null) { return _thread.ManagedThreadId; } return 0; }
        }

        /// <summary>
        /// gets or sets a value indicating the scheduling priority of a thread
        /// </summary>
        public ThreadPriority Priority
        {
            get { if (_thread != null) { return _thread.Priority; } return ThreadPriority.Lowest; }
            set { if (_thread != null) { _thread.Priority = value; } }
        }

        private object _aptState;
        /// <summary>
        /// sets the ApartmentState of a thread before it is started
        /// </summary>
        /// <param name="state">ApartmentState</param>
        public void SetApartmentState(ApartmentState state)
        {
            if (_thread != null) { _thread.SetApartmentState(state); }
            else { _aptState = state; }
        }

        /// <summary>
        /// gets a value containing the states of the current thread
        /// </summary>
        public ThreadState ThreadState
        {
            get { if (_thread != null) { return _thread.ThreadState; } return ThreadState.Unstarted; }
        }

        /// <summary>
        /// returns a System.String that represents the current System.Object
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (_thread != null) { return _thread.ToString(); }
            return base.ToString();
        }

        /// <summary>
        /// sets the ApartmentState of a thread before it is started
        /// </summary>
        /// <param name="state">ApartmentState</param>
        public bool TrySetApartmentState(ApartmentState state)
        {
            if (_thread != null) { return _thread.TrySetApartmentState(state); }
            _aptState = state;
            return false;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead ThoughtWorks
United States United States
Steven A. Lowe is a consultant, software developer, inventor, entrepreneur, author, musician, and lover of puns. He ran an innovative custom software development company for nearly a decade before joining ThoughtWorks as a Principal Consultant in 2014. He admits to being the author of "From Burnout to Bonfire" and a willing participant in the band Noise in the Basement, but neither confirms nor denies being the science-fiction author Steven Ayel.

Comments and Discussions