2009-04-03 9 views
5

Ich suche nach einem allgemeinen Zweck versuchen und versuchen Sie es erneut mit einem Timeout in C#. Grundsätzlich möchte ich folgendes:Allgemeiner Zweck Versuchen Sie und versuchen Sie es erneut mit einem Timeout in C#?

bool stopTrying = false; 
DateTime time = DateTime.Now; 
while (!stopTrying) 
{ 
    try 
    { 
     //[Statement to Execute] 
    } 
    catch (Exception ex) 
    { 
     if (DateTime.Now.Subtract(time).Milliseconds > 10000) 
     { 
      stopTrying = true; 
      throw ex; 
     } 
    } 
} 

Im Fall oben, ich bin für 10 Sekunden warten, aber es sollte eine Variable Timeout basierend auf einem Parameter sein. Ich möchte diesen vollständigen Code nicht überall wiederholen müssen, wo ich ihn brauche. Es gibt mehrere Stellen in meinem Code, wo sie kein Timeout in der API sind und ich werde eine Ausnahme treffen, wenn die Anwendung nicht bereit für die Ausführung der Anweisung ist. Dies würde auch vermeiden, dass Verzögerungen in meiner Anwendung vor dieser Feststellung fest programmiert werden müssen.

Erläuterung: Die fragliche Anweisung könnte etwas wie eine Aufgabe sein. Wenn ich einen Delegaten und method.Invoke verwende, ist nicht die Invokation innerhalb des Delegaten und nicht die ursprüngliche Methode?

Antwort

15

mit Ihrem Beispiel die Lösung ist einfach:

bool DoOrTimeout<T>(T method, TimeSpan timeout) where T : delegate // FIXME 
{ 
    bool stopTrying = false; 
    DateTime time = DateTime.Now; 
    while (!stopTrying) 
    { 
     try 
     { 
      method.Invoke(); 
      stopTrying = true; 
     } 
     catch (Exception ex) 
     { 
      if (DateTime.Now.Subtract(time).Milliseconds > timeout.TotalMilliseconds) 
      { 
       stopTrying = true; 
       throw; 
      } 
     } 
    } 
} 

Rufen Sie einfach DoOrTimeout mit einem Delegierten als ersten Parameter.

+8

Bitte benutzen Sie 'throw ; 'Ausnahmen erneut zu wiederholen, nicht' ex werfen; 'da letzteres den Stac zerstört k Spur. – Will

+0

@Will, ich habe den OP-Code verwendet. Du hast aber recht; 'throw' sollte alleine verwendet werden. Ich werde meine Antwort aktualisieren, um dies zu reflektieren. – strager

+0

Sie vermissen ein stopTrying = true; nach dem Aufruf von "method.Invoke()"; –

0

Erstellen Sie eine Methode, die einen Lambda-Ausdruck für Statement To Execute und einen Parameter für Timeout verwendet. Innerhalb dieser Methode führen Sie den Lambda-Ausdruck innerhalb des try/catch-Blocks aus und verwenden Sie den Parameter für das Timeout.

1

Es ist nicht die schönste Sache, aber ich scheint so gut zu funktionieren. Und es verwendet keine Ausnahmen, um eine Zeitüberschreitung anzuzeigen.

public static class TimeoutOperation 
{ 
    private static readonly TimeSpan DefaultTimeout = new TimeSpan(0, 0, 10); 
    private static readonly TimeSpan DefaultGranularity = new TimeSpan(0, 0, 0, 0, 100); 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action) 
    { 
    return DoWithTimeout<TResult>(action, DefaultTimeout); 
    } 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout) 
    { 
    return DoWithTimeout<TResult>(action, timeout, DefaultGranularity); 
    } 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout, TimeSpan granularity) 
    { 
    Thread thread = BuildThread<TResult>(action); 
    Stopwatch stopwatch = Stopwatch.StartNew(); 
    ThreadResult<TResult> result = new ThreadResult<TResult>(); 

    thread.Start(result); 
    do 
    { 
     if (thread.Join(granularity) && !result.WasSuccessful) 
     { 
     thread = BuildThread<TResult>(action); 
     thread.Start(result); 
     } 

    } while (stopwatch.Elapsed < timeout && !result.WasSuccessful); 
    stopwatch.Stop(); 

    if (thread.ThreadState == System.Threading.ThreadState.Running) 
     thread.Abort(); 

    return result; 
    } 

    private static Thread BuildThread<TResult>(Func<TResult> action) 
    { 
    return new Thread(p => 
    { 
     ThreadResult<TResult> r = p as ThreadResult<TResult>; 
     try { r.Result = action(); r.WasSuccessful = true; } 
     catch (Exception) { r.WasSuccessful = false; } 
    }); 
    } 

    public class ThreadResult<TResult> 
    { 
    public TResult Result { get; set; } 
    public bool WasSuccessful { get; set; } 
    } 
} 
Nutzungs
var result = TimeoutOperation.DoWithTimeout<int>(() => 
    { 
    Thread.Sleep(100); 
    throw new Exception(); 
    }); 
result.WasSuccessful // = false 
result.Value // = 0 

var result = TimeoutOperation.DoWithTimeout<int>(() => 
    { 
    Thread.Sleep(2000); 
    return 5; 
    }); 
result.WasSuccessful // = true 
result.Value // = 5 
1

einen Blick auf diese Frage nehmen. Was Sie fragen, ist genau einer der Verwendungszwecke, die ich beabsichtigt habe.
Implement C# Generic Timeout

WARNUNG: Dieses Beispiel verwendet Thread.Abort. Folgen Sie dem Link zu meiner ursprünglichen Frage, um ein paar Warnungen darüber in den Kommentaren zu lesen.

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

namespace Something 
{ 
    public static class TimeoutWrapper 
    { 
    public static void Invoke(TimeSpan timeout, Action action) 
    { 
     Invoke(timeout, action, null); 
    } 
    public static void Invoke(TimeSpan timeout, Action action, Action abort) 
    { 
     Thread threadToKill = null; 
     Action wrappedAction =() => 
     { 
     threadToKill = Thread.CurrentThread; 
     action(); 
     }; 

     IAsyncResult result = wrappedAction.BeginInvoke(null, null); 
     if (result.AsyncWaitHandle.WaitOne(timeout, true)) 
     { 
     wrappedAction.EndInvoke(result); 
     } 
     else 
     { 
     if (threadToKill != null) 
     { 
      try { threadToKill.Abort(); } 
      catch { /* Ignore */ } 
     } 

     if (abort != null) 
      abort(); 

     throw new TimeoutException(); 
     } 
    } 
    } 
} 

Führen Sie dies einfach in einer Schleife mit entsprechender Timeout-Kontrolle aus.

DateTime endAt = DateTime.Now.AddMinutes(1); 
Timespan timeout = new Timespan(0, 0, 0, 5); 
while(DateTime.Now < endAt) 
{ 
    try 
    { 
     TimeoutWrapper.Invoke(timeout,() => DoSomething()); 
     break; 
    } 
    catch(TimeoutException ex) 
    { /* Do something */ } 
} 
0

Dieser Code ist fehlerhaft (Endlosschleife):

if (DateTime.Now.Subtract(time).Milliseconds > 10000) 

Das richtige ist:

if (DateTime.Now.Subtract(time).TotalMilliseconds > 10000) 
0

Hier ist eine einfache Lösung:

long TIMEOUT = 60000; // 1 minute 
long INTERVAL = 1000; // 1 second 

System.DateTime startTime = System.DateTime.Now;  

while (check_condition()) 
{ 
    System.Threading.Thread.Sleep(INTERVAL); 
    long elapsedTime = System.DateTime.Now.Millisecond - startTime.Millisecond; 

    if (elapsedTime > TIMEOUT) 
    { 
     throw new Exception("Timeout exceeded"); 
    } 
} 
+0

Sie könnten auch einfach "brechen", anstatt eine Ausnahme zu werfen, wenn Sie nicht damit umgehen wollen. – fijiaaron

Verwandte Themen