views:

472

answers:

8

I have a function that I want to allow to run for a given length of time and then, if it hasn't quit on it's own, abort. What is the best way to do this?

The best I have thought of would be to run it in another thread, wait with a timeout for it to die and then use Thread.Abort() to kill it (this might not work if the function has the wrong kind of catch block). Another option (one that I don't know how to make work) would be some kind of preemptive timer with a throw in it.

Is there a better way? Some kind of easy sandbox system?


Edit: the function I'm going to be running doesn't have any system to check if it should cancel and I can't (as in must not) add it. Also, this a a test harness of sorts so the condition under which I will be killing the function is that it has run amuck. In that case I can't count on it doing anything correctly.

+1  A: 

Do everything in your power to find a sane solution to this problem. Exceptions are not flow control, and thread abort is oh so much worse. That said, you could run it on a thread and abort the thread after a timeout.

Edit: Here is a strongly-worded, but valid, explanation of just how much I'm encouraging you to find another solution: http://tdanecker.blogspot.com/2007/08/do-never-ever-use-threadabort.html

280Z28
That's all a good point. However in my case I have a few mitigating considerations: 1) The function can be treated as a pure function because it doesn't interact with outside state, 2) the side effects of not killing it are more or less fatal and 3) I can't touch the code that needs to be sand-boxed so I really do need to do a "stop right now no matter what"
BCS
A: 

You can use Thread.Join() to limit thread execution.

Federico González
+2  A: 

What you want to implement is a BackgroundWorker. The BackgroundWorker can execute a background operation and provides mechanisms for notifying that background operation of cancellation.

The background operation should periodically check the Cancel property on the passed in DoWorkEventArgs instance and gracefully kill itself if the value is true.

Setting the Cancel property on the DoWorkEventArgs instance can be achieved by calling the CancelAsync method on the initiating BackgroundWorker instance.

There is a good example in the MSDN documentation. Also see the community contributions and related links at the bottom of that page.


You may also find this blog post interesting, it uses Thread.Abort though which I would really recommend you avoid... C# Set method timeout using Generics

spoon16
Good general solution +1 but not workable in my case.
BCS
Why? Can you provide details?
spoon16
@spoon16: See my edits and comment. Short version: the same reason that wouldn't work if the function was into a closed source lib.
BCS
Understood, I recommend the AppDomain route then (for a production application). If you are just doing this in a test system you should be able to get away with the blog post I added to my answer.
spoon16
+1  A: 

Why would the function not quit? What result does it produce?

My first try would be to make the code tight enough and handling all the "stuck" cases so you don't need a watchdog -- if you're waiting on resources to be released, put timeouts on the waits, and handle exceptions appropriately.

Short of this, I'd try to spawn an out-of-process watchdog, doing Process.Kill as necessary. This is more effective than Thread.Abort -- if you're going for the brutal solution way, why not go all the way?

dbkk
the watchdog should never fire. But I also should never have bugs. The out of process solution is a bit ugly as getting data back and forth will be ugly.
BCS
+4  A: 

It might be a bit of overkill for something like this, but you could look into whether you want to load whatever's hosting that thread into an AppDomain. An AppDomain is essentially a .NET sandbox.

If the thread goes into the weeds you can just kill the AppDomain.

Michael Burr
Is there a way to get function return values and callback to work across an `AppDomain` boundary?
BCS
Loading the code into an AppDomain is a good idea if you can't use a BackgroundWorker, better than Thread.Abort()
spoon16
There's a pretty good article with some wrapper code for calling arbitrary methods in another AppDomain via a delegate: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx. It worked well for me for getting the results back from the method call, but for some reason I couldn't pass arguments to the delegate without it crashing. Maybe you'll have better luck.
Michael Burr
+1  A: 

I might not be understanding the problem correctly, but why not put the logic into the function itself? Example (C#):

public void someLongFunction()
{
    DateTime start = DateTime.Now;
    while (DateTime.Now <= start.AddMinutes(5)) // assuming 5 mins run time
    {
         // long processing code here
         if (DateTime.Now > start.AddMinutes(5))
             break;
         // more long processing code
    }
    // thread clean up etc. here
}
jkchong
In short: I'm not allowed to.
BCS
A: 

I know this might seem like overkill but it could technically work the way you want. Split the code that you are working on in 2. The first part would be a manager and the second part a executor.

The Executor (in your case) can be a simple command line app with what ever function you want to test. What your manager app does at this point is to call the commandline app using the Process (System.Diagnostics) class. One of the methods of the process class is WaitForExit , which will wait for your console app to exit. But if the app does not exit you can specify a time to wait before forcing the app to exit.

Using this you should be able to get what you want

RC1140
Getting args and return value back and forth makes that overly complicated. I'd almost be more willing to live with the error cases than that.
BCS
+1  A: 

If you can't interrupt easily the process of your function, you may have to use a timer to abort the current thread (it avoids you to execute your function in another thread). As soon as the current thread is aborted, you resert this abort with the Thread.ResetAbort() and you can executes the other steps of your program.

So you can use a class similar to this one

using System.Threading;
using System.Timers;

namespace tools
{
    public class ThreadAbortTimer
    {
        public ThreadAbortTimer(int timeout)
        {
            _CurrentThread = Thread.CurrentThread;
            _Timer = new System.Timers.Timer();
            _Timer.Elapsed += _Timer_Elapsed;
            _Timer.Interval = timeout;
            _Timer.Enable = true;
        }

        /// <summary>
        /// catch the timeout : if the current thread is still valid, it is aborted
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (typeof(ThreadAbortTimer))
            {
                if (_CurrentThread != null)
                {
                    _CurrentThread.Abort();
                    _CurrentThread = null;
                }
            }
        }

        /// <summary>
        /// timer that will check if the process lasts less than 30 seconds
        /// </summary>
        private readonly System.Timers.Timer _Timer;

        /// <summary>
        /// current thread to abort if the process is longer than 30 sec
        /// </summary>
        private Thread _CurrentThread;

        /// <summary>
        /// stop the timer
        /// </summary>
        public void Disable()
        {
            lock (typeof(ThreadAbortTimer))
            {
                _Timer.Enabled = false;
                _CurrentThread = null;
            }
        }

        /// <summary>
        /// dispose the timer
        /// </summary>
        public void Dispose()
        {
            _Timer.Dispose();
        }
    }
}

and then you can use it like this:

   using (var timer = new ThreadAbortTimer(timeout))
    {
        try
        {
            // the process you want to timeout
        }
        catch
        {
            timer.Disable();
            Thread.ResetAbort();
        }
    }
PierrOz
A neat trick +1 but shouldn't that be a finally block?
BCS
You shouldn't "lock on type" though - which is inherently dangerous. Just google for "don't lock on type" for more information. Use a *private* static member (of type object) for that purpose. Or better yet, for that example, use a private instance member - or otherwise all your instances will synchronize with each other.
Christian.K