2013-09-03 29 views
11

Tôi đang tìm một cách hiệu quả để ném một ngoại lệ thời gian chờ nếu một phương thức đồng bộ mất quá nhiều thời gian để thực thi. Tôi đã nhìn thấy một số mẫu nhưng không có gì mà hoàn toàn làm những gì tôi muốn.Giám sát một phương pháp đồng bộ cho thời gian chờ

gì tôi cần phải làm

  1. Kiểm tra rằng phương pháp đồng bộ hóa không vượt quá SLA của nó
  2. Nếu nó ném một ngoại lệ timeout

tôi làm không phải chấm dứt phương thức đồng bộ hóa nếu nó thực thi quá lâu. (Nhiều lần hỏng hóc sẽ làm hỏng bộ ngắt mạch và ngăn chặn lỗi tầng)

Giải pháp của tôi cho đến nay được hiển thị bên dưới. Lưu ý rằng tôi chuyển một CancellationToken sang phương thức đồng bộ với hy vọng rằng nó sẽ đáp ứng yêu cầu hủy bỏ khi hết thời gian chờ. Ngoài ra giải pháp của tôi trả về một nhiệm vụ mà sau đó có thể được chờ đợi trên vv như mong muốn bằng mã gọi của tôi.

Mối quan tâm của tôi là mã này tạo hai nhiệm vụ cho mỗi phương pháp đang được giám sát. Tôi nghĩ TPL sẽ quản lý tốt điều này, nhưng tôi muốn xác nhận.

Điều này có hợp lý không? Có cách nào tốt hơn để làm điều này?

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:

Sử dụng @ câu trả lời Timothy của tôi hiện nay đang sử dụng này. Mặc dù không ít mã hơn nhưng nó rõ ràng hơn rất nhiều. Cảm ơn!

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

Bạn đang sử dụng .NET 4.5 và async/chờ đợi? –

+0

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

+0

Robert: Cảm ơn, mối quan tâm của tôi là Thread.Abort(). Tôi không làm thế. Có vẻ quá quyết liệt. Trong trường hợp của tôi, tôi không cần phải hủy bỏ. – Andre

Trả lời

16

Nếu bạn có một Task gọi task, bạn có thể làm điều này:

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

Nếu timeoutTask.Result kết thúc lên được task, sau đó nó đã không thời gian chờ. Nếu không, đó là delay và đã hết thời gian chờ.

Tôi không biết liệu điều này có hoạt động giống với những gì bạn đã triển khai hay không, nhưng đó là cách tích hợp để thực hiện việc này.

+0

Cảm ơn, trông sạch hơn rất nhiều. Tôi sẽ xem làm thế nào tôi có thể sử dụng điều này để có được hành vi tương tự, sẽ chấp nhận câu trả lời nếu nó tất cả các công trình ra – Andre

1

Tôi đã viết lại giải pháp này cho .NET 4.0 trong đó một số phương pháp không khả dụng, ví dụ: Delay. Phiên bản này đang theo dõi một phương thức trả về object. Làm thế nào để thực hiện Delay trong .NET 4.0 xuất phát từ đây: 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; 
    } 
} 

Làm thế nào để sử dụng nó sau đó trong ứng dụng Console.

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 trên giải pháp sạch Timothy Shields:

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

Giải pháp này tôi thấy cũng sẽ xử lý các trường hợp Task có giá trị trả về - i.e:

async Task<T> 

More được tìm thấy ở đây: MSDN: Crafting a Task.TimeoutAfter Method

Các vấn đề liên quan