Skip to main content

When is it OK to abort a thread? If the problem is that your application won’t shut down because of a runaway thread, people sometimes suggest to run the work in a background thread. Background threads are the same as foreground threads, except they won’t keep your application from exiting if they are still running. So background threads can still block indefinitely or do whatever they want throughout the course of your application. Below is a method to assist in isolating these problem areas.

/// <summary>
/// Helper function to wrap actions within another thread and test to see how long its run.
/// Only allows the Action() to run within the alloted time or it'll abort the wrapped thread.
///
/// BAD PRACTICE TO USE THIS IF YOU CONTROL THE CODE!!!
///
/// This function is mostly for wrapping 3rd party components that are blocking and have the potential to be
/// "runaway". For example, if we're zipping a stream and the input stream continues to grow and the zip library
/// doesn't allow us to exit until the next entry. At which point we'll never be able to cancel. This is a good example
/// of when to use this function
/// </summary>
/// <param name="action">the function to wrap in a timeout</param>
/// <param name="timeout">how long we'll let the function run</param>
/// <param name="description">the name of the timeout thread</param>
/// <param name="checkTimeMs">how often the internal thread should check to see if the timeout has occured</param>
/// <returns>Returns true if the thread executed within the allotted timeout, or false if an exception or timeout occurred</returns>
/// <remarks>from Article... When To Abort A Thread - http://onoffswitch.net/when-to-abort-a-thread/</remarks>
public static bool RunWithTimeout(Action action, TimeSpan timeout, string description, int checkTimeMs = 250)
{
    try
    {
        var startTime = DateTime.Now;
        var thread = ThreadUtil.Start(description + "-TimeoutThread", () => action());

        while (true)
        {
            var runTime = DateTime.Now - startTime;

            if (runTime >= timeout && thread.IsAlive)
            {
                try
                {
                    thread.Abort();
                }
                catch (Exception ex)
                {
                    Log.Error(typeof (TimeoutUtil),
                        String.Format("Unable to abort runaway thread that has excceded timeout {0}",
                            timeout), ex);
                }

                return false;
            }

            if (!thread.IsAlive)
            {
                return true;
            }

            Thread.Sleep(TimeSpan.FromMilliseconds(checkTimeMs));
        }
    }
    catch (Exception ex)
    {
        Log.Error(typeof(TimeoutUtil),
            "Unknown error executing action with timeout", ex);

        return false;
    }
}


[Test]
public void TestTimeout()
{
    var start = DateTime.Now;

    var didTimeOut =
        TimeoutUtil.RunWithTimeout(
                  () => WaitAction(TimeSpan.FromSeconds(2)),
                  TimeSpan.FromSeconds(1),
                  "1secondsWithTimeout");

    var runTime = DateTime.Now - start;

    Console.WriteLine("Action took " + runTime.TotalSeconds);

    Assert.False(didTimeOut);
}

private static void WaitAction(TimeSpan wait)
{
    Thread.Sleep(wait);
}