2013-09-03 12 views
11

Sto cercando un modo efficiente per generare un'eccezione di timeout se un metodo sincrono richiede troppo tempo per essere eseguito. Ho visto alcuni campioni ma niente che faccia esattamente quello che voglio.Monitoraggio di un metodo sincrono per timeout

Che cosa devo fare è

  1. Verificare che il metodo di sincronizzazione non superare il suo SLA
  2. Se non un'eccezione timeout

faccio non devono terminare il metodo di sincronizzazione se viene eseguito troppo a lungo. (Più guasti causano l'intervento di un interruttore automatico e impediscono il guasto a cascata)

La mia soluzione è mostrata di seguito. Si noti che io passo un metodo di sincronizzazione al metodo di sincronizzazione nella speranza che risparmi una richiesta di cancellazione al timeout. Anche la mia soluzione restituisce un'attività che può quindi essere attesa in ecc. Come desiderato dal mio codice di chiamata.

La mia preoccupazione è che questo codice crei due compiti per metodo monitorato. Penso che il TPL gestirà bene, ma vorrei confermarlo.

Ha senso? C'è un modo migliore per farlo?

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:

Utilizzando @ risposta di Timothy ora sto usando questo. Anche se non molto meno codice, è molto più chiaro. Grazie!

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

State usando .NET 4.5 e asincrone/aspettano? –

+0

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

+0

Robert: Grazie, la mia preoccupazione è il Thread.Abort(). Io non lo faccio. Sembra troppo drastico. Nel mio caso non ho bisogno di abortire. – Andre

risposta

16

Se si dispone di un Task chiamato task, si può fare questo:

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

Se timeoutTask.Result finisce per essere task, allora non ha fatto timeout. Altrimenti, è delay ed è scaduto.

Non so se questo si comporterà in modo identico a ciò che è stato implementato, ma è il modo integrato per farlo.

+0

Grazie, sembra molto più pulito. Vedrò come posso usarlo per ottenere un comportamento simile, accetterò la risposta se tutto funziona – Andre

1

Ho riscritto questa soluzione per .NET 4.0 dove alcuni metodi non sono disponibili, ad es. Delay. Questa versione sta monitorando un metodo che restituisce object. Come implementare Delay in .NET 4.0 viene da qui: 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; 
    } 
} 

come usarlo poi nella 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

Per elabolate sulla soluzione pulita Timothy Shields:

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

Questa soluzione Trovo anche gestire il caso in cui l'attività ha un valore di ritorno - i.e:

async Task<T> 

Altro per essere trovato qui: MSDN: Crafting a Task.TimeoutAfter Method

Problemi correlati