Source

‘System.Threading.Thread.Suspend()’ is obsolete: ‘Thread.Suspend has been deprecated…

For time eternal there has been a way to suspend (and by association resume) another thread.  It wasn’t until .NET 2.0 that someone took leadership and conceded that suspending another thread is not a safe thing to do.  .NET 2.0 deprecates System.Threading.Thread.Suspend() and Resume() (although, Resume() isn’t dangerous by association…).

Basically, Suspend() has no care that the thread may actually be managing one or more complex invariants that can’t be changed atomically.  Normally with complex invariants synchronization is used to ensure while one thread is modifying the individual components of an invariant another thread doesn’t try to access it.  This is important because the validity of an invariant is rarely atomic; meaning changing an invariant may take several steps and may be in an unknown or invalid state between the first and the last step (a date is a good example, setting the day and month is two steps; until both steps are complete the date might be invalid).  Suspending a thread circumvents the synchronization primitives and cuts the thread off at the knees.

On a lower level, a thread may be performing a system-wide-synchronized operation like locking a range of bytes in a file, writing to those bytes, unlocking those bytes, then closing the file.  If a thread is suspended before the unlock, that range of bytes will remain locked for an indeterminate amount of time until the thread is resumed or the process terminates.  Not a good thing.  Most threads don’t do all their work with low-level methods and call various higher-level helper methods to perform tasks.  This means the author of a thread’s routine really has no way to determine whether their thread is in a “suspendable” state at any given point in time.  .NET complicates this even further by using locks during execution of class constructors–increasing the likelihood that suspending a thread will cause dire consequences.

Unfortunately, the documentation for Thread.Suspend() really doesn’t offer much in the way of guidance on how to get around this issue with your own threads, it just casually mentions classes Monitor, Mutex, Event and Semaphore; but doesn’t offer any detail on how to use them to put your thread into a wait state–the equivalent of Suspend.

When I create threads they’re meant simply to let my UI be responsive or make use of multiple CPUs (i.e. in both cases they’re CPU bound) and don’t have much need to put a thread into a wait state manually.  But, it’s not without merit.  There are lots of circumstances I don’t normally get into that could benefit from being able to put a thread into a wait state.  It’s also a common question; so, the following is small class that I created: SuspendableThread.

namespace PRI.Threading

{

    using System.Threading;

    abstract class SuspendableThread

    {

        #region Data

        private ManualResetEvent suspendChangedEvent = new ManualResetEvent(false);

        private ManualResetEvent terminateEvent = new ManualResetEvent(false);

        private long suspended;

        private Thread thread;

        private System.Threading.ThreadState failsafeThreadState = System.Threading.ThreadState.Unstarted;

        #endregion Data

 

        public SuspendableThread ( )

        {

        }

 

        private void ThreadEntry ( )

        {

            failsafeThreadState = System.Threading.ThreadState.Stopped;

            OnDoWork();

        }

 

        protected abstract void OnDoWork ( );

 

        #region Protected methods

        protected Boolean SuspendIfNeeded ( )

        {

            Boolean suspendEventChanged = suspendChangedEvent.WaitOne(0, true);

            if (suspendEventChanged)

            {

                Boolean needToSuspend = Interlocked.Read(ref suspended) != 0;

                suspendChangedEvent.Reset();

                if (needToSuspend)

                {

                    /// Suspending…

                    if (1 == WaitHandle.WaitAny(new WaitHandle[] { suspendChangedEvent, terminateEvent }))

                    {

                        return true;

                    }

                    /// …Waking

                }

            }

            return false;

        }

 

        protected bool HasTerminateRequest ( )

        {

            return terminateEvent.WaitOne(0, true);

        }

        #endregion Protected methods

 

        public void Start ( )

        {

            thread = new Thread(new ThreadStart(ThreadEntry));

 

            // make sure this thread won’t be automaticaly

            // terminated by the runtime when the

            // application exits

            thread.IsBackground = false;

 

            thread.Start();

        }

 

        public void Join ( )

        {

            if (thread != null)

            {

                thread.Join();

            }

        }

 

        public Boolean Join ( Int32 milliseconds )

        {

            if (thread != null)

            {

                return thread.Join(milliseconds);

            }

            return true;

        }

 

        /// Not supported in .NET Compact Framework

        public Boolean Join ( TimeSpan timeSpan )

        {

            if (thread != null)

            {

                return thread.Join(timeSpan);

            }

            return true;

        }

 

        public void Terminate ( )

        {

            terminateEvent.Set();

        }

 

        public void TerminateAndWait ( )

        {

            terminateEvent.Set();

            thread.Join();

        }

 

        public void Suspend ( )

        {

            while (1 != Interlocked.Exchange(ref suspended, 1))

            {

            }

            suspendChangedEvent.Set();

        }

 

        public void Resume ( )

        {

            while (0 != Interlocked.Exchange(ref suspended, 0))

            {

            }

            suspendChangedEvent.Set();

        }

 

        public System.Threading.ThreadState ThreadState

        {

            get

            {

                if (null != thread)

                {

                    return thread.ThreadState;

                }

                return failsafeThreadState;

            }

        }

    }

} // namespace

This thread class works a little differently than the run-time’s System.Threading.Thread class in that you can’t just pass it a method from any-old place.  Because we don’t want just anyone getting at the implementation details used to suspend the thread, you have to derive from SuspendableThread and override OnDoWork so your code can gain access to helper methods.

This class has another feature.  If you’re likely to want to initiate thread suspension externally, you’re likely to want to initiate thread termination externally as well.  To that effect this thread also has Terminate() and TerminateAndWait() methods that ask the thread to terminate and will awaken the thread is suspended (something System.Thread.Suspend() and System.Thread.Abort() won’t do).

Using this class is fairly simple.  If you want a suspendable/terminatable thread you simply create a new SuspendableThread-derived class and override OnDoWork with the following pattern:

class MyThread : PRI.Threading.SuspendableThread

{

    protected override void OnDoWork ( )

    {

        try

        {

            while (false == HasTerminateRequest())

            {

                Boolean awokenByTerminate = SuspendIfNeeded();

                if (awokenByTerminate)

                {

                    return;

                }

 

                // TODO: replace the following to lines

                Debug.WriteLine(“doing some work…”);

                Thread.Sleep(450);

            }

        }

        finally

        {

            // TODO: Replace the following line with thread

            // exit processing.

            Debug.WriteLine(“Exiting ThreadEntry()…”);

        }

    }

}

For this class to be useful you generally have to have an iterative algorithm that can test for suspend/terminate during each iteration (the while loop in the above pattern).

To initiate thread suspension, simply call the SuspendableThread.Suspend() method–which asynchronously suspend the thread.  SuspendableThread.Resume() resumes the thread.  For terminate, generally you want to be able to determine when the thread has terminated.  If you want to do that asynchronously  you use the SuspendableThread.Terminate() and SuspendableThread.Join() methods.  To start the terminate process Terminate() is called.  When your asynchronous work is done (i.e. you went on to do something else while the thread was terminating) call Join() to block until the thread is terminated.  If you don’t need to, or don’t have anything to, do while the thread is terminating, use SuspendThread.TerminateAndWait().

This class gives complete control of suspension and termination to the thread.  So, use this class with great care.  You have to write your OnDoWork override in a way that won’t cause deadlocks.  If you don’t have an iterative process, this class isn’t for you.  If each of your iterations takes much time (more than say 500ms) then this class probably isn’t for you either.  If you can’t terminate in the middle of your process, this class also is not for you.  This class will not let the application terminate until the thread routine has exited; so, be sure you initiate termination of the thread before the application exits or you’ll get a deadlock.  The class is a scaled-down version of System.Threading.Thread.  It doesn’t provide all the same goodies System.Threading.Thread does (partially for .NET Compact Framework 2.0 support, and partially for readability) and is intended to be used in limited scenarios.  But, you can extend it to mirror some of System.Threading.Thread’s functionality fairly easily–like Name and Priority properties.

This class was intentionally designed to be compatible with the .NET CF 2.0 (delete Join(TimeSpan) before compiling) but has not been tested with .NET CF 2.0 (if you use the class in .NET CF 2.0, please send me a note and I’ll update this post for the benefit of other readers).

[30-Oct-06 Update: as it turns out, this class is not entirely .NET CF 2.0 compatible, please contact me if you’re interested in a .NET CF 2.0 compatible version]