监视超时的同步方法

如果同步方法执行时间太长,我正在寻找一种有效的方法来抛出超时exception。 我见过一些样品,但没有什么能完全符合我的要求。

我需要做的是

  1. 检查同步方法是否超出其SLA
  2. 如果它确实抛出超时exception

如果执行时间太长,我不必终止同步方法。 (多次故障会使断路器跳闸并防止级联故障)

到目前为止我的解决方案如下所示。 请注意,我确实将CancellationToken传递给了sync方法,希望它能在超时时遵循取消请求。 此外,我的解决方案返回一个任务,然后我可以根据需要等待我的调用代码。

我担心的是,这个代码会为每个监控方法创建两个任务。 我认为TPL会妥善管理,但我想证实。

这有意义吗? 有一个更好的方法吗?

private Task TimeoutSyncMethod( Action 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; } 

编辑:

使用@Timothy的回答我现在正在使用它。 虽然代码没有明显减少,但它更清晰。 谢谢!

  private Task TimeoutSyncMethod( Action 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; } 

如果您有一个名为taskTask ,您可以这样做:

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

如果timeoutTask.Result最终成为task ,那么它没有超时。 否则,它是delay ,它确实超时。

我不知道这是否会与你实现的内容完全相同,但它是内置的方法。

要详细阐述Timothy Shields的清洁解决方案:

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

我发现,这个解决方案也将处理Task有返回值的情况 – 即:

 async Task 

更多内容可以在这里找到: MSDN:制作Task.TimeoutAfter方法

我已经为.NET 4.0重新编写了这个解决方案,其中一些方法不可用,例如Delay 。 此版本正在监视返回object的方法。 如何实现.NET 4.0 Delay来自于: 如何在C#4.0中将任务置于hibernate(或延迟)状态?

 public class OperationWithTimeout { public Task Execute(Func 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 operationTask = Task.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(); Timer timer = new Timer(); timer.Elapsed += (obj, args) => completionSource.TrySetResult(true); timer.Interval = delayTime; timer.AutoReset = false; timer.Start(); return completionSource.Task; } } 

如何在Console应用程序中使用它。

  public static void Main(string[] args) { var operationWithTimeout = new OperationWithTimeout(); TimeSpan timeout = TimeSpan.FromMilliseconds(10000); Func 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(); } 

Jasper的回答让我大部分时间,但我特别想要一个void函数来调用带有超时的非任务同步方法。 这就是我最终得到的结果:

 public static void RunWithTimeout(Action action, TimeSpan timeout) { var task = Task.Run(action); try { var success = task.Wait(timeout); if (!success) { throw new TimeoutException(); } } catch (AggregateException ex) { throw ex.InnerException; } } 

称之为:

 RunWithTimeout(() => File.Copy(..), TimeSpan.FromSeconds(3));