2009-04-03 14 views
5

Estoy buscando un intento de propósito general y reintentar con un tiempo de espera en C#. Básicamente, quiero lo siguiente:Propósito general ¿Intentar y volver a intentar con un tiempo de espera en C#?

bool stopTrying = false; 
DateTime time = DateTime.Now; 
while (!stopTrying) 
{ 
    try 
    { 
     //[Statement to Execute] 
    } 
    catch (Exception ex) 
    { 
     if (DateTime.Now.Subtract(time).Milliseconds > 10000) 
     { 
      stopTrying = true; 
      throw ex; 
     } 
    } 
} 

En el caso anterior, estoy esperando durante 10 segundos, pero debe ser un tiempo de espera variable en función de un parámetro. No quiero tener que repetir este código completo donde sea que necesite usarlo. Hay varios lugares en mi código en los que no se ha agotado el tiempo de espera integrado en la API, y haré clic en una excepción si la aplicación no está lista para la ejecución de la declaración. Esto también evitaría tener que codificar retrasos en mi aplicación antes de este satement.

Aclaración: La afirmación en cuestión podría ser algo así como una tarea. Si utilizo un delegado y un método. Invoque, ¿no está la invocación dentro del delegado y no el método original?

Respuesta

15

Usando su ejemplo, la solución es simple:

bool DoOrTimeout<T>(T method, TimeSpan timeout) where T : delegate // FIXME 
{ 
    bool stopTrying = false; 
    DateTime time = DateTime.Now; 
    while (!stopTrying) 
    { 
     try 
     { 
      method.Invoke(); 
      stopTrying = true; 
     } 
     catch (Exception ex) 
     { 
      if (DateTime.Now.Subtract(time).Milliseconds > timeout.TotalMilliseconds) 
      { 
       stopTrying = true; 
       throw; 
      } 
     } 
    } 
} 

Sólo tiene que llamar DoOrTimeout con un delegado como el primer parámetro.

+8

Utilice 'tiro ; 'volver a lanzar excepciones, no' tirar ex; 'ya que este último destruye el stac k trace. – Will

+0

@Will, utilicé el código OP. Tienes razón, sin embargo; 'throw' debe usarse solo. Actualizaré mi respuesta para reflejar esto. – strager

+0

Falta un stopTrying = true; después de llamar a "method.Invoke()"; –

0

Cree un método que tome una expresión lambda para Statement To Execute y un parámetro para timeout. Dentro de ese método, ejecute la expresión lambda dentro del bloque try/catch y use el parámetro para el tiempo de espera.

1

No es la cosa más bonita, pero parece que funciona muy bien hasta ahora. Y no usa excepciones para indicar un tiempo de espera.

public static class TimeoutOperation 
{ 
    private static readonly TimeSpan DefaultTimeout = new TimeSpan(0, 0, 10); 
    private static readonly TimeSpan DefaultGranularity = new TimeSpan(0, 0, 0, 0, 100); 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action) 
    { 
    return DoWithTimeout<TResult>(action, DefaultTimeout); 
    } 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout) 
    { 
    return DoWithTimeout<TResult>(action, timeout, DefaultGranularity); 
    } 

    public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout, TimeSpan granularity) 
    { 
    Thread thread = BuildThread<TResult>(action); 
    Stopwatch stopwatch = Stopwatch.StartNew(); 
    ThreadResult<TResult> result = new ThreadResult<TResult>(); 

    thread.Start(result); 
    do 
    { 
     if (thread.Join(granularity) && !result.WasSuccessful) 
     { 
     thread = BuildThread<TResult>(action); 
     thread.Start(result); 
     } 

    } while (stopwatch.Elapsed < timeout && !result.WasSuccessful); 
    stopwatch.Stop(); 

    if (thread.ThreadState == System.Threading.ThreadState.Running) 
     thread.Abort(); 

    return result; 
    } 

    private static Thread BuildThread<TResult>(Func<TResult> action) 
    { 
    return new Thread(p => 
    { 
     ThreadResult<TResult> r = p as ThreadResult<TResult>; 
     try { r.Result = action(); r.WasSuccessful = true; } 
     catch (Exception) { r.WasSuccessful = false; } 
    }); 
    } 

    public class ThreadResult<TResult> 
    { 
    public TResult Result { get; set; } 
    public bool WasSuccessful { get; set; } 
    } 
} 
Uso
var result = TimeoutOperation.DoWithTimeout<int>(() => 
    { 
    Thread.Sleep(100); 
    throw new Exception(); 
    }); 
result.WasSuccessful // = false 
result.Value // = 0 

var result = TimeoutOperation.DoWithTimeout<int>(() => 
    { 
    Thread.Sleep(2000); 
    return 5; 
    }); 
result.WasSuccessful // = true 
result.Value // = 5 
1

Tome un vistazo a esta pregunta. Lo que estás pidiendo es exactamente uno de los usos que pretendía.
Implement C# Generic Timeout

ADVERTENCIA: Este ejemplo utiliza Thread.Abort. Siga el enlace a mi pregunta original para leer algunas advertencias al respecto en los comentarios.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 

namespace Something 
{ 
    public static class TimeoutWrapper 
    { 
    public static void Invoke(TimeSpan timeout, Action action) 
    { 
     Invoke(timeout, action, null); 
    } 
    public static void Invoke(TimeSpan timeout, Action action, Action abort) 
    { 
     Thread threadToKill = null; 
     Action wrappedAction =() => 
     { 
     threadToKill = Thread.CurrentThread; 
     action(); 
     }; 

     IAsyncResult result = wrappedAction.BeginInvoke(null, null); 
     if (result.AsyncWaitHandle.WaitOne(timeout, true)) 
     { 
     wrappedAction.EndInvoke(result); 
     } 
     else 
     { 
     if (threadToKill != null) 
     { 
      try { threadToKill.Abort(); } 
      catch { /* Ignore */ } 
     } 

     if (abort != null) 
      abort(); 

     throw new TimeoutException(); 
     } 
    } 
    } 
} 

Simplemente ejecute esto en un bucle con el control de tiempo de espera adecuado.

DateTime endAt = DateTime.Now.AddMinutes(1); 
Timespan timeout = new Timespan(0, 0, 0, 5); 
while(DateTime.Now < endAt) 
{ 
    try 
    { 
     TimeoutWrapper.Invoke(timeout,() => DoSomething()); 
     break; 
    } 
    catch(TimeoutException ex) 
    { /* Do something */ } 
} 
0

Este código es incorrecto (bucle infinito):

if (DateTime.Now.Subtract(time).Milliseconds > 10000) 

el más adecuado es:

if (DateTime.Now.Subtract(time).TotalMilliseconds > 10000) 
0

Aquí es una solución simple:

long TIMEOUT = 60000; // 1 minute 
long INTERVAL = 1000; // 1 second 

System.DateTime startTime = System.DateTime.Now;  

while (check_condition()) 
{ 
    System.Threading.Thread.Sleep(INTERVAL); 
    long elapsedTime = System.DateTime.Now.Millisecond - startTime.Millisecond; 

    if (elapsedTime > TIMEOUT) 
    { 
     throw new Exception("Timeout exceeded"); 
    } 
} 
+0

también podría simplemente "romper" en lugar de lanzar una excepción si no le importa manejarlo. – fijiaaron

Cuestiones relacionadas