实现C#通用超时

我正在寻找一个好的想法来实现一个通用的方式来执行代码的单行(或匿名委托)超时。

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

我正在寻找一个解决方案,可以优雅地在我的代码与气质代码交互(我不能改变)的许多地方实现。

另外,如果可能的话,我想让有问题的“超时”代码停止执行。

这里非常棘手的部分是通过将Action中的执行程序线程传递回可能中止的地方来杀死长时间运行的任务。 我使用一个包装的委托来完成这个任务,这个委托在线程中创建了一个lambda表达式,并把线程传递给一个局部变量。

我提交这个例子,为你的享受。 你真正感兴趣的方法是CallWithTimeout。 这将通过中止它来取消长时间运行的线程,并吞食ThreadAbortException

用法:

 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); } 

做这个工作的静态方法:

  static void CallWithTimeout(Action action, int timeoutMilliseconds) { Thread threadToKill = null; Action wrappedAction = () => { threadToKill = Thread.CurrentThread; try { action(); } catch(ThreadAbortException ex){ Thread.ResetAbort();// cancel hard aborting, lets to finish it nicely. } }; IAsyncResult result = wrappedAction.BeginInvoke(null, null); if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds)) { wrappedAction.EndInvoke(result); } else { threadToKill.Abort(); throw new TimeoutException(); } } } 

我们在生产中大量使用这样的代码

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

实现是开源的,即使在并行计算场景下也能有效地工作,并且可以作为Lokad共享库的一部分

 /// <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); } } // CAUTION: the call to Abort() can be blocking in rare situations // http://msdn.microsoft.com/en-us/library/ty8d3wta.aspx // Hence, it should not be called with the 'lock' as it could deadlock // with the 'finally' block below. 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); } } 

那么,你可以做代表(BeginInvoke,回调设置一个标志 – 和原来的代码等待该标志或超时)的事情 – 但问题是,它是很难关闭正在运行的代码。 例如,杀死(或暂停)一个线程是危险的…所以我不认为有一个简单的方法来强有力地做到这一点。

我会张贴这个,但是注意它不是理想的 – 它不会停止长时间运行的任务,并且在故障时不能正确清理。

  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(); } } 

Pop Catalin最佳答案的一些小改动:

  • 功能而不是行动
  • 抛出错误超时值的异常
  • 在超时情况下调用EndInvoke

已经添加了过载以支持信令工作者取消执行:

 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 } 

我就是这样做的:

 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!"); } } 

我现在只是把它弄明白,所以可能需要一些改进,但会做你想做的。 这是一个简单的控制台应用程序,但演示了所需的原则。

 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."); } } } 

怎么使用Thread.Join(int超时)?

 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"); } 

我的实现创建新的线程和超时后查杀任务如下所示:

 public static void Execute(Action action, int timeout) { Exception exception = null; var thread = new Thread(() => { try { action(); } catch (Exception e) { exception = e; } }); thread.Start(); var completed = thread.Join(timeout); if (!completed) { thread.Abort(); throw new TimeoutException(); } if (exception != null) { throw exception; } }