2013-09-03 10 views
11

Ich bin auf der Suche nach einer effizienten Möglichkeit, eine Timeout-Ausnahme zu werfen, wenn eine synchrone Methode zu lange dauert. Ich habe einige Samples gesehen, aber nichts, was ich will.Überwachung einer synchronen Methode für Timeout

Was ich tun müssen, ist

  1. Überprüfen Sie, ob die Synchronisierungsmethode hat seine SLA überschreiten
  2. Wenn es ein Timeout Ausnahme

ich nicht müssen das beenden wirft Sync-Methode, wenn es zu lange ausgeführt wird. (Mehrere Fehler lösen einen Leistungsschalter aus und verhindern einen Kaskadenfehler)

Meine bisherige Lösung ist unten aufgeführt. Beachten Sie, dass ich ein CancellationToken an die Synchronisierungsmethode weitergebe, in der Hoffnung, dass eine Abbruchanforderung bei Timeout berücksichtigt wird. Auch meine Lösung gibt eine Aufgabe zurück, auf die dann wie von meinem Anrufcode gewünscht gewartet werden kann.

Meine Sorge ist, dass dieser Code erstellt zwei Aufgaben pro Methode Überwachung. Ich denke, die TPL wird das gut hinkriegen, aber ich würde es gerne bestätigen.

Macht das Sinn? Gibt es einen besseren Weg, dies zu tun?

private Task TimeoutSyncMethod(Action<CancellationToken> syncAction, TimeSpan timeout) 
{ 
    var cts = new CancellationTokenSource(); 

    var outer = Task.Run(() => 
    { 
    try 
    { 
     //Start the synchronous method - passing it a cancellation token 
     var inner = Task.Run(() => syncAction(cts.Token), cts.Token); 

     if(!inner.Wait(timeout)) 
     { 
      //Try give the sync method a chance to abort grecefully 
      cts.Cancel(); 
      //There was a timeout regardless of what the sync method does - so throw 
      throw new TimeoutException("Timeout waiting for method after " + timeout); 
     } 
    } 
    finally 
    { 
     cts.Dispose(); 
    } 
    }, cts.Token); 

    return outer; 
} 

Edit:

@ Antwort des Timothy verwendet nun diese mit mir. Während nicht wesentlich weniger Code ist es viel klarer. Vielen Dank!

private Task TimeoutSyncMethod(Action<CancellationToken> syncAction, TimeSpan timeout) 
    { 
    var cts = new CancellationTokenSource(); 

    var inner = Task.Run(() => syncAction(cts.Token), cts.Token); 
    var delay = Task.Delay(timeout, cts.Token); 

    var timeoutTask = Task.WhenAny(inner, delay).ContinueWith(t => 
     { 
     try 
     { 
      if(!inner.IsCompleted) 
      { 
      cts.Cancel(); 
      throw new TimeoutException("Timeout waiting for method after " + timeout); 
      } 
     } 
     finally 
     { 
      cts.Dispose(); 
     } 
     }, cts.Token); 

    return timeoutTask; 
    } 
+0

Sind Sie .NET 4.5 und Asynchron/await mit:

async Task<T> 

Mehr hier zu finden? –

+0

http://stackoverflow.com/questions/299198/implement-c-sharp-generic-timeout –

+0

Robert: Danke, meine Sorge dort ist der Thread.Abort(). Ich mache das nicht. Scheint zu drastisch. In meinem Fall muss ich nicht abbrechen. – Andre

Antwort

16

Wenn Sie eine Task genannt task, können Sie dies tun:

var delay = Task.Delay(TimeSpan.FromSeconds(3)); 
var timeoutTask = Task.WhenAny(task, delay); 

Wenn timeoutTask.Result endet task zu sein, dann tat es nicht Timeout. Ansonsten ist es delay und es hat Timeout.

Ich weiß nicht, ob sich das genauso verhält wie das, was Sie implementiert haben, aber es ist die eingebaute Methode, dies zu tun.

+0

Danke, das sieht viel sauberer aus. Ich werde sehen, wie ich das verwenden kann, um ein ähnliches Verhalten zu bekommen, werde die Antwort akzeptieren, wenn alles klappt – Andre

1

Ich habe diese Lösung für .NET 4.0 umgeschrieben, wo einige Methoden nicht verfügbar sind, z. Delay. Diese Version überwacht eine Methode, die object zurückgibt. Wie implementieren Delay in .NET 4.0 von hier kommt: How to put a task to sleep (or delay) in C# 4.0?

public class OperationWithTimeout 
{ 
    public Task<object> Execute(Func<CancellationToken, object> operation, TimeSpan timeout) 
    { 
     var cancellationToken = new CancellationTokenSource(); 

     // Two tasks are created. 
     // One which starts the requested operation and second which starts Timer. 
     // Timer is set to AutoReset = false so it runs only once after given 'delayTime'. 
     // When this 'delayTime' has elapsed then TaskCompletionSource.TrySetResult() method is executed. 
     // This method attempts to transition the 'delayTask' into the RanToCompletion state. 
     Task<object> operationTask = Task<object>.Factory.StartNew(() => operation(cancellationToken.Token), cancellationToken.Token); 
     Task delayTask = Delay(timeout.TotalMilliseconds); 

     // Then WaitAny() waits for any of the provided task objects to complete execution. 
     Task[] tasks = new Task[]{operationTask, delayTask}; 
     Task.WaitAny(tasks); 

     try 
     { 
      if (!operationTask.IsCompleted) 
      { 
       // If operation task didn't finish within given timeout call Cancel() on token and throw 'TimeoutException' exception. 
       // If Cancel() was called then in the operation itself the property 'IsCancellationRequested' will be equal to 'true'. 
       cancellationToken.Cancel(); 
       throw new TimeoutException("Timeout waiting for method after " + timeout + ". Method was to slow :-)"); 
      } 
     } 
     finally 
     { 
      cancellationToken.Dispose(); 
     } 

     return operationTask; 
    } 

    public static Task Delay(double delayTime) 
    { 
     var completionSource = new TaskCompletionSource<bool>(); 
     Timer timer = new Timer(); 
     timer.Elapsed += (obj, args) => completionSource.TrySetResult(true); 
     timer.Interval = delayTime; 
     timer.AutoReset = false; 
     timer.Start(); 
     return completionSource.Task; 
    } 
} 

Wie benutzt man es dann in Console App.

public static void Main(string[] args) 
    { 
     var operationWithTimeout = new OperationWithTimeout(); 
     TimeSpan timeout = TimeSpan.FromMilliseconds(10000); 

     Func<CancellationToken, object> operation = token => 
     { 
      Thread.Sleep(9000); // 12000 

      if (token.IsCancellationRequested) 
      { 
       Console.Write("Operation was cancelled."); 
       return null; 
      } 

      return 123456; 
     }; 

     try 
     { 
      var t = operationWithTimeout.Execute(operation, timeout); 
      var result = t.Result; 
      Console.WriteLine("Operation returned '" + result + "'"); 
     } 
     catch (TimeoutException tex) 
     { 
      Console.WriteLine(tex.Message); 
     } 

     Console.WriteLine("Press enter to exit"); 
     Console.ReadLine(); 
    } 
1

Um auf Timothy Shields saubere Lösung elabolate:

 if (task == await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(3)))) 
     { 
      return await task; 
     } 
     else 
      throw new TimeoutException(); 

Diese Lösung werde ich auch den Fall behandeln, wo die Aufgabe, einen Rückgabewert hat - i.e: MSDN: Crafting a Task.TimeoutAfter Method

Verwandte Themen