2011-01-31 7 views
8

Sto sviluppando un'applicazione WPF 4.0 in cui otteniamo i dati da un servizio Web remoto. Il servizio web espone circa 120 metodi ai suoi clienti. Se una chiamata al servizio web dalla mia applicazione WPF fallisce, ho bisogno di riprovare n volte che è configurabile tramite App.Config. Come implementarlo? Ci sono schemi di progettazione che affrontano questo problema?Come implementare di nuovo n volte in caso di eccezione in C#?

+4

Si noti che questo modello non si integra bene con se stesso. Se una funzione che tenta quattro volte chiama una funzione che tenta quattro volte, e che chiama una funzione che prova quattro volte, allora l'ultima operazione viene ripetuta 64 volte. Se attende 30 secondi tra i tentativi, l'utente rimane lì per mezz'ora in attesa del messaggio di errore. Consiglio vivamente contro questo modello. Quando qualcosa non va a buon fine, fermati * immediatamente *, informa l'utente e lascia che decidano se riprovare o se andare a vedere se il router è scollegato. –

+0

Ofcourse per le app WPF, il numero di tentativi non sarà alto! Tuttavia, questo è molto utile per le applicazioni console che eseguono operazioni in background. – funwithcoding

+0

Cosa hai finito per fare? Non dimenticare di segnare una risposta? –

risposta

6
static T TryNTimes<T>(Func<T> func, int times) 
{ 
    while (times>0) 
    { 
    try 
    { 
     return func(); 
    } 
    catch(Exception e) 
    { 
     if (--times <= 0) 
      throw; 
    } 

    } 
} 
+0

Dovrebbe essere chiamato TryNTimes, non ExecuteNTimes. –

+0

Dovrebbe, lo modificherò. Grazie. –

3

Ho scritto questo codice non molto tempo fa per fare qualcosa di simile a quello che vuoi. Può essere modificato per adattarsi alle tue esigenze. È un metodo di attesa generico. Passare una funzione e se il risultato atteso non viene restituito, attendere quindi riprovare e uscire dopo il numero X di tentativi.

/// <summary> 
    /// Wait for the result of func to return the expeceted result 
    /// </summary> 
    /// <param name="func">Function to execute each cycle</param> 
    /// <param name="result">Desired result returned by func</param> 
    /// <param name="waitInterval">How long to wait (ms) per cycle </param> 
    /// <param name="cycles">How many times to execute func before failing</param> 
    /// <returns>True if desired result was attained. False if specified time runs out before desired result is returned by func</returns> 
    protected static bool WaitForEvent(Func<bool> func, bool result, int waitInterval, int cycles) 
    { 
     int waitCount = 0; 
     while (func() != result) 
     { 
      if (waitCount++ < cycles) 
      { 
       Thread.Sleep(waitInterval); 
      } 
      else 
      { 
       return false; 
      } 
     } 

     return true; 

    } 
+2

Stava per scrivere la stessa cosa. Ben fatto :-) –

+0

Potrebbe essere una buona idea aumentare l'intervallo di attesa ad ogni tentativo. – CodesInChaos

+0

thats if (waitCount ++

1
while(retries < maxTries) 
    try 
    { 
     //retryable code here 
     break; 
    } 
    catch(Exception ex) 
    { 
     if(++retries == maxTries) 
     throw; 
     continue; 
    } 

Certamente niente di particolare, ma sarà ottenere il lavoro fatto. Il modello principale, che sarebbe comune a quasi tutte le implementazioni, è un costrutto di loop che contiene e in qualche modo controllato da un try-catch; che può essere una chiamata ricorsiva o un loop iterativo come il ciclo while sopra. Assicurati di uscire correttamente dal ciclo dopo un tentativo riuscito e tieni traccia dei tentativi; l'incapacità di fare causa causerà un ciclo infinito.

+0

E se lo inserisce in una funzione, ha un punto centrale per le chiamate al metodo 120+. –

+0

L'intervallo di attesa non sembra definito – NotMe

+1

L'attesa non è sempre necessaria; dipende da cosa esattamente stai cercando di fare nel blocco try. Se è necessario accertarsi che un computer remoto abbia completato la pulizia dopo un tentativo fallito, attendere con ogni mezzo. Tuttavia, ThreadSleep() deve essere utilizzato con attenzione o causerà l'interruzione della risposta dell'app; che allarga la portata di ciò che devi fare per implementare qualcosa di simile in modo pulito. – KeithS

0
while(true) 
{ 

try 
{ 
Method(); 
break; 
} 
catch(Exception ex) 
{ 
i++; 
if(i == n) throw ex; 
} 

} 
0

Ecco un codice simile che avvolge IO violazione di condivisione. E 'la stessa idea: abbiamo un delegato e un metodo statico involucro:

/// <summary> 
/// Defines a sharing violation wrapper delegate. 
/// </summary> 
public delegate void WrapSharingViolationsCallback(); 

/// <summary> 
/// Wraps sharing violations that could occur on a file IO operation. 
/// </summary> 
/// <param name="action">The action to execute. May not be null.</param> 
/// <param name="exceptionsCallback">The exceptions callback. May be null.</param> 
/// <param name="retryCount">The retry count.</param> 
/// <param name="waitTime">The wait time in milliseconds.</param> 
public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime) 
{ 
    if (action == null) 
     throw new ArgumentNullException("action"); 

    for (int i = 0; i < retryCount; i++) 
    { 
     try 
     { 
      action(); 
      return; 
     } 
     catch (IOException ioe) 
     { 
      if ((IsSharingViolation(ioe)) && (i < (retryCount - 1))) 
      { 
       bool wait = true; 
       if (exceptionsCallback != null) 
       { 
        wait = exceptionsCallback(ioe, i, retryCount, waitTime); 
       } 
       if (wait) 
       { 
        Thread.Sleep(waitTime); 
       } 
      } 
      else 
      { 
       throw; 
      } 
     } 
    } 
} 

E poi, noi lo chiamiamo in questo modo (si adatta espressione lambda perfettamente qui):

WrapSharingViolations(() => DoWhatever(...)); 
0

è possibile utilizzare una funzionalità approccio a questo:

class Program 
{ 
    static T Retry<T, TException>(Func<T> thingToTry, int timesToRetry) 
     where TException : Exception 
    { 
     // Start at 1 instead of 0 to allow for final attempt 
     for (int i = 1; i < timesToRetry; i++) 
     { 
      try 
      { 
       return thingToTry(); 
      } 
      catch (TException) 
      { 
       // Maybe: Trace.WriteLine("Failed attempt..."); 
      } 
     } 

     return thingToTry(); // Final attempt, let exception bubble up 
    } 

    static int ServiceCall() 
    { 
     if (DateTime.Now.Ticks % 2 == 0) 
     { 
      throw new InvalidOperationException("Randomly not working"); 
     } 

     return DateTime.Now.Second; 
    } 

    static void Main() 
    { 
     int s = Retry<int, InvalidOperationException>(ServiceCall, 10); 
    } 
} 

È possibile utilizzarlo per rilevare eccezioni specifiche (aggiungere ulteriori parametri generici TException se necessario).

+1

Perché utilizzare ricorsivo? Ci saranno problemi di prestazioni e di memoria, per non menzionare (come hai sottolineato) una possibilità di overflow dello stack. Buona prova, ma pessimo design IMO. –

+0

Buon punto. Ultimamente sto facendo troppa codifica funzionale. Fisso. –

+0

Sebbene sia necessario riprovare molto prima che si verifichino problemi di prestazioni e memoria. –

0

Potreste essere in grado di fare questo con un GOTO(gasp)

int count = 0; 
try 
{ 
    TryAgain: 
    // Do something with your web service 
} 
catch(Exception e) 
{ 
    if(count < numberOfAttemptsAllowed) 
    { 
     count++; 
     goto TryAgain; 
    } 
} 

Sono sicuro che ci potrebbe essere un modo migliore, ma questo potrebbe fare quello che ti serve.

Problemi correlati