2013-09-03 17 views
11

Szukam skutecznego sposobu na wyrzucenie wyjątku limitu czasu, jeśli wykonanie metody synchronicznej trwa zbyt długo. Widziałem kilka próbek, ale nic, co całkiem robi to, co chcę.Monitorowanie synchronicznej metody limitu czasu

Co muszę zrobić, to

  1. Sprawdź, czy metoda synchronizacji nie przekracza jego SLA
  2. Jeśli tak rzucać wyjątek Timeout

zrobić nie trzeba rozwiązać metoda synchronizacji, jeśli jest wykonywana zbyt długo. (Wielokrotne awarie wyzwolą wyłącznik i zapobiegną awariom kaskadowania)

Moje dotychczasowe rozwiązanie jest pokazane poniżej. Zwróć uwagę, że przekazuję Token Anulowania do metody synchronizacji w nadziei, że będzie honorować żądanie anulowania po upływie limitu czasu. Również moje rozwiązanie zwraca zadanie, które można następnie oczekiwać na itp. Zgodnie z życzeniem mojego kodu wywołującego.

Obawiam się, że ten kod tworzy dwa zadania na monitorowaną metodę. Myślę, że TPL sobie z tym poradzi, ale chciałbym to potwierdzić.

Czy to ma sens? Czy jest lepszy sposób to zrobić?

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:

Korzystanie @ odpowiedź Timothy'ego Obecnie używam tego. O ile nie jest to znacznie mniejszy kod, jest on o wiele jaśniejszy. Dzięki!

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

Czy używasz .NET 4.5 i asynchroniczny/Oczekujcie? –

+0

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

+0

Robert: Dzięki, moja troska jest Thread.Abort(). Nie chcę tego robić. Wydaje się zbyt drastyczny. W moim przypadku nie muszę przerwać. – Andre

Odpowiedz

16

Jeśli masz Task nazwie task, można to zrobić:

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

Jeśli timeoutTask.Result kończy się task, to nie limit czasu. W przeciwnym razie jest to delay i upłynął limit czasu.

Nie wiem, czy to będzie zachowywać się identycznie z tym, co zaimplementowałeś, ale jest to wbudowany sposób, aby to zrobić.

+0

Dzięki, że wygląda o wiele czystsze. Zamierzam zobaczyć, jak mogę to wykorzystać, aby uzyskać podobne zachowanie, zaakceptuję odpowiedź, jeśli wszystko się ułoży – Andre

1

Przepisałem to rozwiązanie na .NET 4.0, gdzie niektóre metody nie są dostępne, np. Delay. Ta wersja monitoruje metodę, która zwraca object. Jak wdrożyć Delay w .NET 4.0 pochodzi stąd: 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; 
    } 
} 

Jak go używać następnie w aplikacji konsoli.

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

Aby elabolate na Timothy Shields czystym roztworze:

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

Rozwiązanie to znajdę będzie również obsługiwać przypadek, w którym zadanie ma wartość zwracaną - Nie.e:

async Task<T> 

Więcej można znaleźć tutaj: MSDN: Crafting a Task.TimeoutAfter Method