Implementieren Sie das generische C # -Zeitlimit

Ich bin auf der Suche nach guten Ideen für die Implementierung eines generischen Wegs, um eine einzelne Zeile (oder einen anonymen Delegaten) Code mit einem Timeout auszuführen.

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

Ich suche nach einer Lösung, die elegant an vielen Orten implementiert werden kann, wo mein Code mit temperamentalem Code interagiert (den ich nicht ändern kann).

Außerdem möchte ich, dass der störende “Zeitüberschreitung” -Code nicht weiter ausgeführt wird, wenn dies möglich ist.

   

Der wirklich knifflige Teil war, die langwierige Aufgabe zu beenden, indem der Executor-Thread von der Action zurück an eine Stelle geschickt wurde, an der er abgebrochen werden konnte. Ich habe dies mit der Verwendung eines umschlossenen Delegaten erreicht, das den Thread zum Töten in eine lokale Variable in der Methode, die das Lambda erstellt hat, ausgibt.

Ich unterbreite dieses Beispiel zu Ihrem Vergnügen. Die Methode, die Sie wirklich interessiert, ist CallWithTimeout. Dadurch wird der lange laufende Thread abgebrochen, indem er abgebrochen und die ThreadAbortException verschluckt wird :

Verwendung:

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

Die statische Methode, die die Arbeit erledigt:

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

Wir verwenden Code wie diesen stark in der Produktion :

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

Die Implementierung ist open-source, arbeitet auch in parallelen Rechnerszenarien effizient und ist als Teil von Lokad Shared Libraries verfügbar

 ///  /// Helper class for invoking tasks with timeout. Overhead is 0,005 ms. ///  /// The type of the result. [Immutable] public sealed class WaitFor { readonly TimeSpan _timeout; ///  /// Initializes a new instance of the  class, /// using the specified timeout for all operations. ///  /// The timeout. public WaitFor(TimeSpan timeout) { _timeout = timeout; } ///  /// Executes the spcified function within the current thread, aborting it /// if it does not complete within the specified timeout interval. ///  /// The function. /// result of the function ///  /// 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. ///  /// if function is null /// if the function does not finish in time  public TResult Run(Func 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); } } } ///  /// Executes the spcified function within the current thread, aborting it /// if it does not complete within the specified timeout interval. ///  /// The timeout. /// The function. /// result of the function ///  /// 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. ///  /// if function is null /// if the function does not finish in time  public static TResult Run(TimeSpan timeout, Func function) { return new WaitFor(timeout).Run(function); } } 

Dieser Code ist immer noch errorshaft, Sie können mit diesem kleinen Testprogramm versuchen:

  static void Main(string[] args) { // Use a sb instead of Console.WriteLine() that is modifying how synchronous object are working var sb = new StringBuilder(); for (var j = 1; j < 10; j++) // do the experiment 10 times to have chances to see the ThreadAbortException for (var ii = 8; ii < 15; ii++) { int i = ii; try { Debug.WriteLine(i); try { WaitFor.Run(TimeSpan.FromMilliseconds(10), () => { Thread.Sleep(i); sb.Append("Processed " + i + "\r\n"); return i; }); } catch (TimeoutException) { sb.Append("Time out for " + i + "\r\n"); } Thread.Sleep(10); // Here to wait until we get the abort procedure } catch (ThreadAbortException) { Thread.ResetAbort(); sb.Append(" *** ThreadAbortException on " + i + " *** \r\n"); } } Console.WriteLine(sb.ToString()); } } 

Es gibt eine Wettlaufbedingung. Es ist eindeutig möglich, dass eine ThreadAbortException ausgetriggers wird, nachdem die Methode WaitFor.Run() aufgerufen wurde. Ich habe keine zuverlässige Methode gefunden, dies zu beheben, aber mit dem gleichen Test kann ich kein Problem mit der TheSoftwareJedi- Antwort beantworten.

Bildbeschreibung hier eingeben

Nun, Sie könnten Dinge mit Delegaten machen (BeginInvoke, wobei ein Callback ein Flag setzt – und der ursprüngliche Code auf dieses Flag oder Timeout wartet) – aber das Problem ist, dass es sehr schwer ist, den laufenden Code herunterzufahren. Zum Beispiel ist das Töten (oder Pausieren) eines Threads gefährlich … also glaube ich nicht, dass es einen einfachen Weg gibt, dies robust zu tun.

Ich werde dies posten, aber beachte, dass es nicht ideal ist – es stoppt die langwierige Aufgabe nicht, und es reinigt nicht richtig bei einem Fehler.

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

Einige kleine Änderungen an Pop Catalins großer Antwort:

  • Func statt Action
  • Ausnahmebedingung für ungültigen Timeout-Wert
  • Aufruf von EndInvoke im Falle eines Timeouts

Overloads wurden hinzugefügt, um signaling worker zu unterstützen, die Ausführung abzubrechen:

 public static T Invoke (Func 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 (Func function, TimeSpan timeout) { return Invoke (args => function (), timeout); // ignore CancelEventArgs } public static void Invoke (Action action, TimeSpan timeout) { Invoke (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 } 

So würde ich es machen:

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

Ich habe das jetzt einfach weggeworfen, also könnte es eine Verbesserung brauchen, aber ich werde tun, was Sie wollen. Es ist eine einfache Konsole App, aber zeigt die Prinzipien, die benötigt werden.

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

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