2013-09-03 2 views
11

Я ищу эффективный способ выбросить исключение тайм-аута, если синхронный метод занимает слишком много времени для выполнения. Я видел несколько образцов, но ничего, что делает то, что я хочу.Мониторинг синхронного метода для тайм-аута

Что мне нужно сделать, это

  1. Убедитесь, что синхронизация метод не превышает его ОАС
  2. Если это бросить исключение тайм-аут

я не должны прекратить действие sync, если он выполняется слишком долго. (Несколько отказов отключат автоматический выключатель и предотвратит каскадный отказ)

Мое решение пока показано ниже. Обратите внимание, что я передаю метод CancellationToken методу синхронизации в надежде, что он выполнит запрос на отмену таймаута. Также мое решение возвращает задачу, которую можно затем ожидать по иному по моему коду вызова.

Я обеспокоен тем, что этот код создает две задачи для каждого проверяемого метода. Я думаю, что TPL справится с этим хорошо, но я хотел бы подтвердить.

Имеет ли это смысл? Есть лучший способ сделать это?

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:

Используя @ ответ Тимофея сейчас я использую это. Хотя не намного меньше кода, это намного яснее. Благодаря!

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

Вы используете .NET 4.5 и асинхронной/Await? –

+0

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

+0

Robert: Спасибо, моя забота о том, что есть Thread.Abort(). Я этого не делаю. Кажется, слишком резкий. В моем случае мне не нужно прерывать. – Andre

ответ

16

Если у вас есть Task под названием task, вы можете сделать это:

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

Если timeoutTask.Result заканчивается время task, то это не тайм-аут. В противном случае это delay, и он сделал тайм-аут.

Я не знаю, будет ли это вести себя идентично тому, что вы реализовали, но это встроенный способ сделать это.

+0

Спасибо, что выглядит намного чище. Я собираюсь посмотреть, как я могу использовать это, чтобы получить подобное поведение, примет ответ, если все это сработает – Andre

1

Я переписал это решение для .NET 4.0, где некоторые методы недоступны, например. Delay. Эта версия контролирует метод, который возвращает object. Как реализовать Delay в .NET 4.0 приходит отсюда: 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; 
    } 
} 

Как использовать его затем в консоли приложения.

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

Для elabolate на чистом растворе Тимоти Shields:

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

Это решение я считаю, будет также обрабатывать случай, когда задача имеет возвращаемое значение - я.е:

async Task<T> 

Больше можно найти здесь: MSDN: Crafting a Task.TimeoutAfter Method