views:

11458

answers:

8

I am looking for good ideas for implementing a generic way to have a single line (or anonymous delegate) of code execute with a timeout.

TemperamentalClass tc = new TemperamentalClass();
tc.DoSomething();  // normally runs in 30 sec.  Want to error at 1 min

I'm looking for a solution that can elegantly be implemented in many places where my code interacts with temperamental code (that I can't change).

In addition, I would like to have the offending "timed out" code stopped from executing further if possible.

+5  A: 

Well, you could do things with delegates (BeginInvoke, with a callback setting a flag - and the original code waiting for that flag or timeout) - but the problem is that it is very hard to shut down the running code. For example, killing (or pausing) a thread is dangerous... so I don't think there is an easy way to do this robustly.

I'll post this, but note it is not ideal - it doesn't stop the long-running task, and it doesn't clean up properly on failure.

    static void Main()
    {
        DoWork(OK, 5000);
        DoWork(Nasty, 5000);
    }
    static void OK()
    {
        Thread.Sleep(1000);
    }
    static void Nasty()
    {
        Thread.Sleep(10000);
    }
    static void DoWork(Action action, int timeout)
    {
        ManualResetEvent evt = new ManualResetEvent(false);
        AsyncCallback cb = delegate {evt.Set();};
        IAsyncResult result = action.BeginInvoke(cb, null);
        if (evt.WaitOne(timeout))
        {
            action.EndInvoke(result);
        }
        else
        {
            throw new TimeoutException();
        }
    }
    static T DoWork<T>(Func<T> func, int timeout)
    {
        ManualResetEvent evt = new ManualResetEvent(false);
        AsyncCallback cb = delegate { evt.Set(); };
        IAsyncResult result = func.BeginInvoke(cb, null);
        if (evt.WaitOne(timeout))
        {
            return func.EndInvoke(result);
        }
        else
        {
            throw new TimeoutException();
        }
    }
Marc Gravell
I'm perfectly happy killing something that's gone rouge on me. Its still better than letting it eat CPU cycles until the next reboot (this is part of a windows service).
chilltemp
In that case, consider spawning an AppDomain that you can kill...
Marc Gravell
result.AsyncWaitHandle can be used, no manual reset needed
TheSoftwareJedi
+2  A: 

I just knocked this out now so it might need some improvement, but will do what you want. It is a simple console app, but demonstrates the principles needed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;


namespace TemporalThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Action action = () => Thread.Sleep(10000);
            DoSomething(action, 5000);
            Console.ReadKey();
        }

        static void DoSomething(Action action, int timeout)
        {
            EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
            AsyncCallback callback = ar => waitHandle.Set();
            action.BeginInvoke(callback, null);

            if (!waitHandle.WaitOne(timeout))
                throw new Exception("Failed to complete in the timeout specified.");
        }
    }

}
Jason Jackson
Nice. The only thing I would add is that he might prefer to throw System.TimeoutException rather than just System.Exception
Joel Coehoorn
Oh, yeah: and I'd wrap that in it's own class as well.
Joel Coehoorn
result.AsyncWaitHandle can be used, no manual reset needed.
TheSoftwareJedi
+27  A: 

The really tricky part here was killing the long running task through passing the executor thread from the Action back to a place where it could be aborted. I accomplished this with the use of a wrapped delegate that passes out the thread to kill into a local variable in the method that created the lambda.

I submit this example, for your enjoyment. The method you are really interested in is CallWithTimeout. This will cancel the long running thread by aborting it, and swallowing the ThreadAbortException:

Usage:

class Program
{

    static void Main(string[] args)
    {
        //try the five second method with a 6 second timeout
        CallWithTimeout(FiveSecondMethod, 6000);

        //try the five second method with a 4 second timeout
        //this will throw a timeout exception
        CallWithTimeout(FiveSecondMethod, 4000);
    }

    static void FiveSecondMethod()
    {
        Thread.Sleep(5000);
    }

The static method doing the work:

    static void CallWithTimeout(Action action, int timeoutMilliseconds)
    {
        Thread threadToKill = null;
        Action wrappedAction = () =>
        {
            threadToKill = Thread.CurrentThread;
            action();
        };

        IAsyncResult result = wrappedAction.BeginInvoke(null, null);
        if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
        {
            wrappedAction.EndInvoke(result);
        }
        else
        {
            threadToKill.Abort();
            throw new TimeoutException();
        }
    }

}
TheSoftwareJedi
Why the catch(ThreadAbortException)? AFAIK you cannot really catch a ThreadAbortException (it will be rethrown after when the catch block is left).
csgero
You my friend, are absolutely correct. We can just ignore it in this case and let the thread die. The pool will be replenished, but this is something to take note of if this is expected to happen a lot. This *CAN BE* a performance issue.
TheSoftwareJedi
Just as an aside, I'm not sure how this works in .NET. In java you'd have to declare the Thread object as final which would prevent it's mutating. Somehow the CLR geniuses made this work. Kudos.
TheSoftwareJedi
Thread.Abort() is very dangerous to use, It shouldn't be used with regular code, only code that is guaranteed to be safe should be aborted, such as code that is Cer.Safe, uses constrained execution regions and safe handles. It shouldn't be done for any code.
Pop Catalin
Thread.Abort() is bad. Very bad!!! for unguarded code, see here: http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=c1898a31-a0aa-40af-871c-7847d98f1641
Pop Catalin
chilltemp
Yes, Thread.Abort is bad, but sometimes other outcomes are worse.
TheSoftwareJedi
Shouldn't there be a result.AsyncWaitHandle.Close();to handle the cleanup?
BozoJoe
I can't believe this is the accepted answer, someone must not be reading the comments here, or the answer was accepted before the comments and that person does not check his replies page. Thread.Abort is not a solution, it's just another problem you need to solve!
Lasse V. Karlsen
You are the one not reading the comments. As chilltemp says above, he's calling code that he has NO control over - and wants it to abort. He has no option other than Thread.Abort() if he wants this to run within his process. You are right that Thread.Abort is bad - but like chilltemp says, other things are worse!
TheSoftwareJedi
@TheSoftwareJedi, if you abort a thread the next best thing you should do is unload the application domain the thread was running in.
Pop Catalin
@Pop Feel free to edit the code and add that feature! Having previously worked with @chilltemp, I know what he's up against here. There is no option he has aside from Abort.
TheSoftwareJedi
+3  A: 

This is how I'd do it:

public static class Runner
{
    public static void Run(Action action, TimeSpan timeout)
    {
        IAsyncResult ar = action.BeginInvoke(null, null);
        if (ar.AsyncWaitHandle.WaitOne(timeout))
            action.EndInvoke(ar); // This is necesary so that any exceptions thrown by action delegate is rethrown on completion
        else
            throw new TimeoutException("Action failed to complete using the given timeout!");
    }
}
Pop Catalin
this doesn't stop the executing task
TheSoftwareJedi
Not all tasks are safe to stop, all kinds of issues can arrive, deadlocks, resource leakage, corruption of state... It shouldn't be done in the general case.
Pop Catalin
+3  A: 

Some minor changes to Pop Catalin's great answer:

  • Func instead of Action
  • Throw exception on bad timeout value
  • Calling EndInvoke in case of timeout

Overloads have been added to support signaling worker to cancel execution:

public static T Invoke<T> (Func<CancelEventArgs, T> function, TimeSpan timeout) {
    if (timeout.TotalMilliseconds <= 0)
        throw new ArgumentOutOfRangeException ("timeout");

    CancelEventArgs args = new CancelEventArgs (false);
    IAsyncResult functionResult = function.BeginInvoke (args, null, null);
    WaitHandle waitHandle = functionResult.AsyncWaitHandle;
    if (!waitHandle.WaitOne (timeout)) {
        args.Cancel = true; // flag to worker that it should cancel!
        /* •————————————————————————————————————————————————————————————————————————•
           | IMPORTANT: Always call EndInvoke to complete your asynchronous call.   |
           | http://msdn.microsoft.com/en-us/library/2e08f6yc(VS.80).aspx           |
           | (even though we arn't interested in the result)                        |
           •————————————————————————————————————————————————————————————————————————• */
        ThreadPool.UnsafeRegisterWaitForSingleObject (waitHandle,
            (state, timedOut) => function.EndInvoke (functionResult),
            null, -1, true);
        throw new TimeoutException ();
    }
    else
        return function.EndInvoke (functionResult);
}

public static T Invoke<T> (Func<T> function, TimeSpan timeout) {
    return Invoke (args => function (), timeout); // ignore CancelEventArgs
}

public static void Invoke (Action<CancelEventArgs> action, TimeSpan timeout) {
    Invoke<int> (args => { // pass a function that returns 0 & ignore result
        action (args);
        return 0;
    }, timeout);
}

public static void TryInvoke (Action action, TimeSpan timeout) {
    Invoke (args => action (), timeout); // ignore CancelEventArgs
}
George Tsiokos
+14  A: 

We are using code like this heavily in production:

var result = WaitFor<Result>.Run(1.Minutes(), () => service.GetSomeFragileResult());

Implementation is open-sourced, works efficiently even in parallel computing scenarios and is available as a part of Lokad Shared Libraries

/// <summary>
/// Helper class for invoking tasks with timeout. Overhead is 0,005 ms.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
[Immutable]
public sealed class WaitFor<TResult>
{
 readonly TimeSpan _timeout;

 /// <summary>
 /// Initializes a new instance of the <see cref="WaitFor{T}"/> class, 
 /// using the specified timeout for all operations.
 /// </summary>
 /// <param name="timeout">The timeout.</param>
 public WaitFor(TimeSpan timeout)
 {
  _timeout = timeout;
 }

 /// <summary>
 /// Executes the spcified function within the current thread, aborting it
 /// if it does not complete within the specified timeout interval. 
 /// </summary>
 /// <param name="function">The function.</param>
 /// <returns>result of the function</returns>
 /// <remarks>
 /// The performance trick is that we do not interrupt the current
 /// running thread. Instead, we just create a watcher that will sleep
 /// until the originating thread terminates or until the timeout is
 /// elapsed.
 /// </remarks>
 /// <exception cref="ArgumentNullException">if function is null</exception>
 /// <exception cref="TimeoutException">if the function does not finish in time </exception>
 public TResult Run(Func<TResult> function)
 {
  if (function == null) throw new ArgumentNullException("function");

  var sync = new object();
  var isCompleted = false;

  WaitCallback watcher = obj =>
   {
    var watchedThread = obj as Thread;

    lock (sync)
    {
     if (!isCompleted)
     {
      Monitor.Wait(sync, _timeout);
     }

     if (!isCompleted)
     {
      watchedThread.Abort();
     }
    }
   };

  try
  {
   ThreadPool.QueueUserWorkItem(watcher, Thread.CurrentThread);
   return function();
  }
  catch (ThreadAbortException)
  {
   // This is our own exception.
   Thread.ResetAbort();

   throw new TimeoutException(string.Format("The operation has timed out after {0}.", _timeout));
  }
  finally
  {
   lock (sync)
   {
    isCompleted = true;
    Monitor.Pulse(sync);
   }
  }
 }

 /// <summary>
 /// Executes the spcified function within the current thread, aborting it
 /// if it does not complete within the specified timeout interval.
 /// </summary>
 /// <param name="timeout">The timeout.</param>
 /// <param name="function">The function.</param>
 /// <returns>result of the function</returns>
 /// <remarks>
 /// The performance trick is that we do not interrupt the current
 /// running thread. Instead, we just create a watcher that will sleep
 /// until the originating thread terminates or until the timeout is
 /// elapsed.
 /// </remarks>
 /// <exception cref="ArgumentNullException">if function is null</exception>
 /// <exception cref="TimeoutException">if the function does not finish in time </exception>
 public static TResult Run(TimeSpan timeout, Func<TResult> function)
 {
  return new WaitFor<TResult>(timeout).Run(function);
 }
}
Rinat Abdullin
This what I implemented, It can handle parameters and return value, which I prefer and needed. Thanks Rinat
Gabriel Mongeon
Kudos! This is beautiful!
Markus
+1  A: 

What about using Thread.Join(int timeout)?

public static void CallWithTimeout(Action act, int millisecondsTimeout)
{
    var thread = new Thread(new ThreadStart(act));
    thread.Start();
    if (!thread.Join(millisecondsTimeout))
     throw new Exception("Timed out");
}
That would notify the calling method of a problem, but not abort the offending thread.
chilltemp
I'm not sure that's correct. It's not clear from the documentation what happens to the worker thread when the Join timeout elapses.
Matthew Lowe
A: 

Many solutions about it, which is the best ?? thanks

Sorry, I haven't really looked at this since I implemented TheSoftwareJedi's solution. I can tell you that it has worked exactly as intended without side-affects for over a year now. That being said, you should read and re-read all the comments about the dangers of Thread.Abort before implementing anything on this page.
chilltemp