Do not suspend or abort threads

I’ve said a number of times that calling Thread.Abort is like stopping a car by shooting the driver in the head. The car will stop, but there’s no telling what damage it’ll do in the process.

Calling Thread.Abort throws an exception that will break execution. You can catch ThreadAbortException and even prevent it from killing your thread, but you can’t prevent the thread abort from breaking execution. For example, say you have a thread that’s executing this method:

    void MyThreadProc(object state)
    {
        while (true) // go forever
        {
            // process an item
        }
        Console.WriteLine("Thread exited normally.");
    }

If the main thread calls Abort on that thread, the thread will terminate. Whatever the thread was doing is aborted and the thread exits the method without writing anything to the console.

The first and most important problem here is that when you call Abort, whatever is happening in that loop is interrupted. Your program could be in the middle of a critical multi-part data update, could be holding a mutex, have allocated critical system resources, etc. When the thread aborted, the update is left unfinished. Unless you’re religious about using try/finally, any mutex you held continues to be held, critical system resources aren’t released, etc. Even if you do clean up allocated resources, you still have the problem of an unfinished data update. Your program is left in an unknown and therefore potentially corrupt state from which you can’t reliably recover.

Catching ThreadAbortException helps in that you can probably structure your code so that the exception handler does some cleanup and even notifies you that the thread was terminated unexpectedly.

    void MyThreadProc(object state)
    {
        try
        {
            while (true) // go forever
            {
                // process an item
            }
            Console.WriteLine("Thread exited normally.");
        }
        catch (ThreadAbortException)
        {
            Console.WriteLine("Thread aborted.");
            // do cleanup here
        }
        Console.WriteLine("End of thread.");
    }

Now if you abort the thread, the catch block is executed. Still, whatever the thread was doing inside the loop is interrupted. And ThreadAbortException is special. Unlike most other exceptions, which are done once you catch them, ThreadAbortException is re-thrown by the runtime. You would never see the “End of thread” message here if the thread is aborted.

The discussion above should disabuse you of the notion that depending on Thread.Abort is a good idea. But let’s say that you know your thread isn’t doing any of those potentially dangerous things, so you think you’re safe.

Then comes the next problem: you can’t guarantee that Thread.Abort will actually abort the thread. The thread could catch ThreadAbortException and then call Thread.ResetAbort to prevent the exception from being re-thrown. That prevents Thread.Abort from terminating the thread. For example:

    void MyThreadProc(object state)
    {
        while (true)
        {
            try
            {
                while (true) // go forever
                {
                    // process an item
                }
                Console.WriteLine("Thread exited normally.");
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine("No! I'll stop when I'm good and ready.");
                Thread.ResetAbort();
            }
        }
        Console.WriteLine("End of thread.");
    }

That doesn’t prevent Abort from interrupting the loop, though. Even with >ResetAbort, whatever is happening in the inner loop is vulnerable. You can’t prevent Abort from interrupting the thread.

Don’t make the mistake of thinking that surrounding your critical code with calls to Thread.BeginCriticalRegion and Thread.EndCriticalRegion will solve the problem. Those might prevent the runtime host from throwing ThreadAbortException, true. It might instead just tear down the app domain, terminating the entire program. Oops.

It should be clear by now that calling Thread.Abort is either dangerous or useless. There is no good reason to use it. Use cancellation if you need the ability to terminate threads. That way, the thread cooperates in the shutdown and nothing critical gets interrupted; the thread can exit when it is safe to do so.

As bad as Thread.Abort is, Thread.Suspend is even worse. You can potentially write code that gracefully handles a thread abort because all relevant finally blocks are executed when ThreadAbortException is thrown. The aborted thread could have finally blocks that free allocated resources, release locks, etc. But Thread.Suspend? That stops a thread in its tracks. The thread just halts. Instantly. Whatever it was doing is suspended until some code calls Thread.Resume. There is no unwinding of the stack or executing finally blocks. The. thread. just. stops. So locks are held, resources remain allocated, updates remain “in progress,” etc. The thread is frozen in time.

That’s never a good idea.

There are much safer ways to make threads suspend processing. Perhaps the easiest is to implement something similar to cancellation, using a ManualResetEventSlim. For example:

    ManualResetEvent ContinueFlag = new ManualResetEventSlim(true);

    void MyThreadProc(object state)
    {
        while (ContinueFlag.Wait())
        {
            // process
        }
    }

The call to Wait will block until something sets the event. The event remains set until some code resets it. If you want to pause the thread, simply call ContinueFlag.Reset. The thread will finish the current iteration of the loop, and then block on the call to Wait until some other thread calls ContinueFlag.Set.

If you want to support cancellation as well as pausing, you can pass the cancellation token to Wait, like this:

   try
    {
        while (ContinueFlag.Wait(cancelToken))
        {
            // process
        }
    }
    catch (OperationCancelledException)
    {
        Console.WriteLine("Cancellation requested.");
    }

Again, the thread participates in the cancellation, so whatever is happening in the loop is not affected by the cancellation.

If the continue flag is a ManualResetEvent, you’ll have to use a technique similar to that described in How to: Listen for Cancellation Requests That Have Wait Handles.

Your best bet is to forget that Thread.Abort, Thread.Suspend, and Thread.Resume even exist. I can think of very few good uses for Abort (think of it as the goto of multithreaded programming), and no good use for Suspend and Resume. Don’t use them. If you do, you’ll regret it.

3 comments to Do not suspend or abort threads

  • What you say is the conventional wisdom. However, I think the advice is unnecessarily conservative in many cases.

    If you’re hand coding parallel code, the easiest solution is generally to avoid mutating any global state. If you’re doing that anyway; and you’re not calling libraries that mutate global state (i.e. no I/O, no unsafe code, only simple, straightforward computation), then Thread.Abort is fairly safe.

    But more importantly: the criticism you make can be made of almost any multithreaded .NET code, even without Thread.Abort! The basis of your complaint is that you cannot be sure that a thread is mutating global state in an abort-safe fashion – it could be halfway a critical section as you say. But really, .NET offers *no* guarantees of generalized thread safety in any case. Even if you *don’t* use Thread.Abort, there’s no guarrantee that a thread won’t mutate some global state in conflict with some other thread. There’s no guarrantee a thread is locking properly, or indeed that a thread event has code that *can* be made thread safe by locking.

    The point is that *any* multithreading assumes frienly, cooperative threads. Sure, a thread might call ResetAbort, but that’s a nonsense argument – a thread might also cause a stackoverflow, or kill the process, or ignore your cancellation requests.

    Particularly that last bit is non-trivial: the alternative to thread.abort in a multithreaded scenario is cooperative cancellation, but that only works well if there are many checks for cancellation; and when there are, that the exceptions typically used to detect cancellation don’t cause state corruption.

    Even if you do implement cancellation properly, you’re now saddled with larger, slower code (compared to the thread.abort scenario), and worse: you’re imposing a behavioral requirement on all “slow” reachable code on that thread; this is a maintenance burder you’ll be paying every time you do anything to that code.

    If your code is primarily event orchestration, cancellation may work fine. If it’s complex calcuations, I think it’s an anti-pattern best avoided. What you really want is functional-style code: no externally observable side effects. Perhaps you need to enforce that using process separation; but Thread.Abort has its place too.

    TL;DR: You want side-effect free (functional) programming to simplify parallel computation anyhow, and in truly functional code Thread.Abort is safe.

    Assume that your threads are not executing unsafe code, nor mutating global state

  • Jim

    That’s one way to think of it, and you make many valid arguments. My experience, though, is that threads often have to mutate global state: in particular database updates (which admittedly should be transacted) and disk files (more difficult to transact).

    I think a large part of the difference of opinion here has to do with the way that we use threads. My work typically doesn’t involve a lot of heavy computation. Rather, my threads are usually long-running tasks that service queues in complex producer-consumer applications. I’ve found in those situations that cancellation makes for cleaner code that’s easier to write, maintain, and debug. It’s easier to instruct less experienced programmers on the proper way to handle cancellation than it is to instruct them on the details of properly handling a ThreadAbortException.

    I can see your points about using Thread.Abort on threads that do nothing but compute a value and return it.

  • Yeah, as soon as your threads are doing any I/O or any inter-thread communication (probably even when lock-free), Thread.Abort becomes extremely tricky. And the downsides of cancellation are less serious for “talkative” threads – the complexity in controlling where cancellation is checked is fairly intrinsic to what you’re trying to do since you need to think about where termination is safe. Similarly, the extra CPU cost in continuing execution unnecessarily isn’t that relevant when the CPU isn’t busy anyhow.