Il modo più pulito per scrivere la logica dei tentativi?


455

Occasionalmente ho bisogno di riprovare un'operazione più volte prima di arrendermi. Il mio codice è come:

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

Vorrei riscriverlo in una funzione di ripetizione generale come:

TryThreeTimes(DoSomething);

È possibile in C #? Quale sarebbe il codice per il TryThreeTimes()metodo?


1
Un ciclo semplice non è abbastanza? Perché semplicemente non ripetere più volte ed eseguire la logica?
Restuta,

13
Personalmente, sarei estremamente diffidente nei confronti di tale metodo di supporto. È certamente possibile implementare usando lambda, ma il modello stesso è estremamente puzzolente, quindi introdurre un aiuto per esso (il che implica che viene ripetuto frequentemente) è di per sé altamente sospetto e suggerisce fortemente un cattivo design complessivo.
Pavel Minaev,

12
Nel mio caso, i miei DoSomething () stanno facendo cose su macchine remote come la cancellazione di file o il tentativo di colpire una porta di rete. In entrambi i casi, ci sono importanti problemi di temporizzazione per quando DoSomething avrà successo e, a causa della lontananza, non è possibile ascoltare alcun evento. Quindi sì, è puzzolente. Suggerimenti benvenuti.
noctonura,

13
@PavelMinaev perché l'uso dei tentativi suggerisce un cattivo design complessivo? Se scrivi molto codice che collega i punti di integrazione, l'uso dei tentativi è sicuramente uno schema che dovresti prendere seriamente in considerazione.
bytedev,

Risposte:


569

Le dichiarazioni catch catch che semplicemente ritentano la stessa chiamata possono essere pericolose se utilizzate come meccanismo generale di gestione delle eccezioni. Detto questo, ecco un wrapper di tentativi basato su lambda che puoi usare con qualsiasi metodo. Ho scelto di considerare il numero di tentativi e il timeout dei tentativi come parametri per un po 'più di flessibilità:

public static class Retry
{
    public static void Do(
        Action action,
        TimeSpan retryInterval,
        int maxAttemptCount = 3)
    {
        Do<object>(() =>
        {
            action();
            return null;
        }, retryInterval, maxAttemptCount);
    }

    public static T Do<T>(
        Func<T> action,
        TimeSpan retryInterval,
        int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();

        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    Thread.Sleep(retryInterval);
                }
                return action();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }
}

È ora possibile utilizzare questo metodo di utilità per eseguire la logica dei tentativi:

Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));

o:

Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));

o:

int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);

Oppure potresti persino fare un asyncsovraccarico.


7
+1, in particolare per l'avviso e il controllo degli errori. Sarei più a mio agio se questo passasse nel tipo di eccezione da catturare come parametro generico (dove T: Exception), comunque.
TrueWill,

1
Il mio intento era che "tentativi" in realtà significavano tentativi. Ma non è troppo difficile cambiarlo per significare "tentativi". Finché il nome è mantenuto significativo. Esistono altre opportunità per migliorare il codice, come la verifica di tentativi negativi o timeout negativi, ad esempio. Ho omesso questi principalmente per mantenere l'esempio semplice ... ma, di nuovo, in pratica questi sarebbero probabilmente dei buoni miglioramenti all'implementazione.
LBushkin,

40
Usiamo un modello simile per l'accesso al nostro DB in un'app Biztalk ad alto volume, ma con due miglioramenti: abbiamo liste nere per le eccezioni che non dovrebbero essere ripetute e memorizziamo la prima eccezione che si verifica e la gettiamo quando il tentativo alla fine fallisce. Il motivo è che la seconda e le seguenti eccezioni sono spesso diverse dalla prima. In tal caso si nasconde il problema iniziale quando si ricodifica solo l'ultima eccezione.
TToni,

2
@Dexters Lanciamo una nuova eccezione con l'eccezione originale come eccezione interna. La traccia dello stack originale è disponibile come attributo dalle eccezioni interne.
TToni

7
Puoi anche provare a utilizzare una libreria open source come Polly per gestirlo. C'è molta più flessibilità nell'attesa tra i tentativi ed è stato validato da molti altri che hanno utilizzato il progetto. Esempio: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen,

222

Dovresti provare Polly . È una libreria .NET scritta da me che consente agli sviluppatori di esprimere in modo fluente politiche transitorie di gestione delle eccezioni come Retry, Retry Forever, Wait and Retry o Circuit Breaker.

Esempio

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());

1
Cos'è in realtà il delegato di OnRetry? Presumo che sia ciò che dobbiamo eseguire quando si verifica un'eccezione. Pertanto, quando si verifica un'eccezione, il delegato di OnRetry chiamerà e successivamente eseguirà il delegato. È così?
user6395764

61

Questa è probabilmente una cattiva idea. In primo luogo, è emblematico della massima "la definizione di follia sta facendo la stessa cosa due volte e si aspetta ogni volta risultati diversi". In secondo luogo, questo modello di codifica non si compone bene con se stesso. Per esempio:

Supponiamo che il tuo livello hardware di rete rinvii un pacchetto tre volte in caso di errore, aspettando, diciamo, un secondo tra i guasti.

Supponiamo ora che il livello software rinvii una notifica relativa a un errore tre volte in caso di errore del pacchetto.

Supponiamo ora che il livello di notifica riattivi la notifica tre volte in caso di errore di consegna della notifica.

Supponiamo ora che il livello di segnalazione errori riattivi il livello di notifica tre volte in caso di errore di notifica.

E ora supponiamo che il server web riattivi la segnalazione degli errori tre volte in caso di errore.

E ora supponiamo che il client Web rinvii la richiesta tre volte dopo aver ricevuto un errore dal server.

Supponiamo ora che la linea sullo switch di rete che dovrebbe instradare la notifica all'amministratore sia scollegata. Quando l'utente del client Web riceve finalmente il messaggio di errore? Lo faccio dopo circa dodici minuti.

Per non pensare che questo sia solo un esempio sciocco: abbiamo visto questo bug nel codice cliente, anche se molto, molto peggio di quanto ho descritto qui. Nel particolare codice cliente, il divario tra la condizione di errore che si verificava e veniva infine segnalato all'utente era di diverse settimane perché così tanti livelli si stavano riprovando automaticamente con le attese. Immagina cosa succederebbe se ci fossero dieci tentativi anziché tre .

Di solito la cosa giusta da fare con una condizione di errore è segnalarla immediatamente e lasciare che l'utente decida cosa fare. Se l'utente desidera creare una politica di tentativi automatici, lascia che crei tale politica al livello appropriato nell'astrazione del software.


19
+1. Raymond condivide un esempio di vita reale qui, blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi

210
-1 Questo consiglio è inutile per guasti transitori alla rete incontrati da sistemi automatizzati di elaborazione batch.
no,

15
Non sono sicuro se questo sta dicendo "Non farlo" seguito da "Fallo". La maggior parte delle persone che pongono questa domanda sono probabilmente le persone che lavorano nell'astrazione del software.
Jim L

44
Quando si dispone di processi batch di lunga durata che utilizzano risorse di rete, come i servizi Web, non è possibile aspettarsi che la rete sia affidabile al 100%. Ci saranno timeout occasionali, disconnessioni dei socket, forse anche anomalie di routing spurie o interruzioni del server che si verificano durante l'utilizzo. Un'opzione è fallire, ma ciò può significare riavviare un lungo lavoro in un secondo momento. Un'altra opzione è quella di riprovare alcune volte con adeguato ritardo per vedere se si tratta di un problema temporaneo, quindi fallire. Sono d'accordo sulla composizione, di cui devi essere consapevole .. ma a volte è la scelta migliore.
Erik Funkenbusch,

18
Penso che la citazione che hai usato all'inizio della tua risposta sia interessante. "Aspettarsi risultati diversi" è una follia solo se l'esperienza precedente ti dà regolarmente gli stessi risultati. Mentre il software è basato su una promessa di coerenza, ci sono sicuramente circostanze in cui siamo tenuti a interagire con forze inaffidabili al di fuori del nostro controllo.
Michael Richardson,

49
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

Quindi chiameresti:

TryThreeTimes(DoSomething);

... o in alternativa ...

TryThreeTimes(() => DoSomethingElse(withLocalVariable));

Un'opzione più flessibile:

public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            Thread.Sleep(sleepPeriod);
        }
   }
}

Da usare come:

DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);

Una versione più moderna con supporto per async / await:

public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            await action();
            return; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            await Task.Delay(sleepPeriod);
        }
   }
}

Da usare come:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);

2
preferibilmente cambia if in: --retryCount <= 0perché andrà avanti per sempre se vuoi disabilitare i tentativi impostandolo su 0. Tecnicamente il termine retryCountnon è un buon nome, perché non riproverà se lo imposti su 1. Rinomina al tryCounto mettere il - dietro.
Stefanvds,

2
@saille Sono d'accordo. Tuttavia l'OP (e tutte le altre risposte) stanno usando Thread.Sleep. Le alternative sono usare i timer, o più probabilmente oggi asyncper riprovare, con Task.Delay.
Drew Noakes,

2
Ho aggiunto una versione asincrona.
Drew Noakes,

Rompere solo se l'azione returns true? Func<bool>
Kiquenet,

32

Il blocco di applicazioni per la gestione dei guasti transitori fornisce una raccolta estensibile di strategie di nuovo tentativo, tra cui:

  • incrementale
  • Intervallo fisso
  • Back-off esponenziale

Include anche una raccolta di strategie di rilevamento degli errori per i servizi basati su cloud.

Per ulteriori informazioni, consultare questo capitolo della Guida per gli sviluppatori.

Disponibile via NuGet (cerca ' topazio ').


1
Interessante. Puoi usarlo al di fuori di Windows Azure, ad esempio in un'app Winforms?
Matthew Lock,

6
Assolutamente. Utilizzare il meccanismo di riprova principale e fornire le proprie strategie di rilevamento. Abbiamo intenzionalmente disaccoppiato quelli. Trova il pacchetto core nuget qui: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik

2
Inoltre, il progetto è ora sotto Apache 2.0 e accetta i contributi della community. aka.ms/entlibopen
Grigori Melnik,

1
@Alex. I suoi pezzi stanno entrando nella piattaforma.
Grigori Melnik

2
Questo è ora deprecato, e l'ultima volta che l'ho usato conteneva alcuni bug che per quanto ne so non lo erano, e non saranno mai corretti: github.com/MicrosoftArchive/… .
Ohad Schneider,

15

Consentire funzioni e riprovare i messaggi

public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
 Guard.IsNotNull(method, "method");            
 T retval = default(T);
 do
 {
   try
   {
     retval = method();
     return retval;
   }
   catch
   {
     onFailureAction();
      if (numRetries <= 0) throw; // improved to avoid silent failure
      Thread.Sleep(retryTimeout);
   }
} while (numRetries-- > 0);
  return retval;
}

RetryMethod to retval are True, oppuremax retries?
Kiquenet,

14

Potresti anche considerare di aggiungere il tipo di eccezione per cui riprovare. Ad esempio, si tratta di un'eccezione di timeout che si desidera riprovare? Un'eccezione del database?

RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);

public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
    if (action == null)
        throw new ArgumentNullException("action");
    if (retryOnExceptionType == null)
        throw new ArgumentNullException("retryOnExceptionType");
    while (true)
    {
        try
        {
            action();
            return;
        }
        catch(Exception e)
        {
            if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
                throw;

            if (retryTimeout > 0)
                System.Threading.Thread.Sleep(retryTimeout);
        }
    }
}

Potresti anche notare che tutti gli altri esempi hanno un problema simile con il test per i tentativi == 0 e riprovano all'infinito o non riescono a sollevare eccezioni quando viene dato un valore negativo. Anche Sleep (-1000) fallirà nei blocchi catch sopra. Dipende da quanto "sciocco" ti aspetti che la gente sia, ma la programmazione difensiva non fa mai male.


9
+1, ma perché non fare RetryForException <T> (...) dove T: Exception, quindi catch (T e)? L'ho appena provato e funziona perfettamente.
TrueWill,

O qui o dal momento che non ho bisogno di fare nulla con il Tipo a condizione che ho pensato che un vecchio parametro semplice avrebbe fatto il trucco.
csharptest.net,

@TrueWill apparentemente catch (T ex) ha alcuni bug in base a questo post stackoverflow.com/questions/1577760/...
csharptest.net

3
Aggiornamento: in realtà un'implementazione migliore che sto usando richiede un delegato Predicato <Exception> che restituisce true se un nuovo tentativo è appropriato. Ciò consente di utilizzare codici di errore nativi o altre proprietà dell'eccezione per determinare se un nuovo tentativo è applicabile. Ad esempio codici HTTP 503.
csharptest.net,

1
"Anche Sleep (-1000) fallirà nei blocchi catch sopra" ... usa un TimeSpan e non otterrai questo problema. Inoltre TimeSpan è molto più flessibile e auto-descrittivo. Dalla tua firma di "int retryTimeout" come faccio a sapere se retryTimeout è MS, secondi, minuti, anni ?? ;-)
bytedev

13

Sono un fan dei metodi di ricorsione e di estensione, quindi ecco i miei due centesimi:

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}

7

Basandomi sul lavoro precedente, ho pensato di migliorare la logica dei tentativi in ​​tre modi:

  1. Specifica del tipo di eccezione da catturare / riprovare. Questa è l'entusiasmo principale in quanto riprovare per qualsiasi eccezione è semplicemente sbagliato.
  2. Non annidare l'ultimo tentativo in un tentativo / cattura, ottenendo prestazioni leggermente migliori
  3. Rendendolo un Actionmetodo di estensione

    static class ActionExtensions
    {
      public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
      {
        if (action == null)
          throw new ArgumentNullException("action");
    
        while( retries-- > 0 )
        {
          try
          {
            action( );
            return;
          }
          catch (T)
          {
            Thread.Sleep( retryDelay );
          }
        }
    
        action( );
      }
    }

Il metodo può quindi essere invocato in questo modo (è possibile utilizzare anche metodi anonimi):

new Action( AMethodThatMightThrowIntermittentException )
  .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );

1
Questo è eccellente Ma personalmente non lo definirei "retryTimeout" in quanto non è davvero un timeout. "RetryDelay", forse?
Holf,

7

Mantienilo semplice con C # 6.0

public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
    try
    {
        return action();
    }
    catch when (retryCount != 0)
    {
        await Task.Delay(retryInterval);
        return await Retry(action, retryInterval, --retryCount);
    }
}

2
Sono un po 'curioso, questo genererebbe una quantità folle di thread con un alto numero di tentativi e un intervallo a causa della restituzione dello stesso metodo attendibile?
HuntK24,

6

Usa Polly

https://github.com/App-vNext/Polly-Samples

Ecco un tentativo generico che uso con Polly

public T Retry<T>(Func<T> action, int retryCount = 0)
{
    PolicyResult<T> policyResult = Policy
     .Handle<Exception>()
     .Retry(retryCount)
     .ExecuteAndCapture<T>(action);

    if (policyResult.Outcome == OutcomeType.Failure)
    {
        throw policyResult.FinalException;
    }

    return policyResult.Result;
}

Usalo in questo modo

var result = Retry(() => MyFunction()), 3);

5

Implementata la risposta di LBushkin all'ultima moda:

    public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }

                await task();
                return;
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }

    public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }
                return await task();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }  

e per usarlo:

await Retry.Do([TaskFunction], retryInterval, retryAttempts);

mentre la funzione [TaskFunction]può essere Task<T>o solo Task.


1
Grazie Fabian! Questo dovrebbe essere votato fino in cima!
JamesHoux,

1
@MarkLauter la risposta breve è sì. ;-)
Fabian Bigler,

4

Lo implementerei:

public static bool Retry(int maxRetries, Func<bool, bool> method)
{
    while (maxRetries > 0)
    {
        if (method(maxRetries == 1))
        {
            return true;
        }
        maxRetries--;
    }
    return false;        
}

Non userei le eccezioni come sono usate negli altri esempi. Mi sembra che se ci aspettiamo la possibilità che un metodo non abbia successo, il suo fallimento non fa eccezione. Quindi il metodo che sto chiamando dovrebbe restituire true se ha esito positivo e false se non è riuscito.

Perché è un Func<bool, bool>e non solo un Func<bool>? Quindi, se voglio che un metodo sia in grado di generare un'eccezione in caso di errore, ho modo di informarlo che si tratta dell'ultimo tentativo.

Quindi potrei usarlo con un codice come:

Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       if (!succeeded && lastIteration)
       {
          throw new InvalidOperationException(...)
       }
       return succeeded;
   });

o

if (!Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       return succeeded;
   }))
{
   Console.WriteLine("Well, that didn't work.");
}

Se passare un parametro che il metodo non utilizza si rivela imbarazzante, è banale implementare un sovraccarico di Retry che richiede anche un Func<bool>.


1
+1 per evitare l'eccezione. Anche se farei un vuoto Riprova (...) e lancerei qualcosa? I ritorni booleani e / oi codici di ritorno sono troppo spesso trascurati.
csharptest.net,

1
"se ci aspettiamo la possibilità che un metodo non abbia successo, il suo fallimento non è un'eccezione" - sebbene ciò sia vero in alcuni casi, l'eccezione non deve implicare un eccezionale. È per la gestione degli errori. Non vi è alcuna garanzia che il chiamante controllerà un risultato booleano. Vi è la garanzia che un'eccezione verrà gestita (dal runtime che arresta l'applicazione se non altro).
TrueWill,

Non riesco a trovare il riferimento ma credo che .NET definisca un'eccezione come "un metodo non ha fatto quello che ha detto che farà". 1 scopo è utilizzare le eccezioni per indicare un problema piuttosto che il modello Win32 di richiedere al chiamante di controllare il valore restituito se la funzione ha avuto successo o meno.
noctonura,

Ma le eccezioni non si limitano a "indicare un problema". Includono anche una massa di informazioni diagnostiche che richiedono tempo e memoria per la compilazione. Esistono chiaramente situazioni in cui ciò non ha importanza. Ma ci sono molte cose in cui lo fa. .NET non usa eccezioni per il flusso di controllo (confronta, diciamo, con l'uso StopIterationdell'eccezione da parte di Python ), e c'è un motivo.
Robert Rossney,

Il TryDomodello del metodo è una pendenza scivolosa. Prima di conoscerlo, l'intero stack di chiamate sarà costituito da TryDometodi. Sono state inventate eccezioni per evitare un tale casino.
HappyNomad,


2

Per coloro che desiderano avere sia l'opzione per riprovare su qualsiasi eccezione o impostare esplicitamente il tipo di eccezione, utilizzare questo:

public class RetryManager 
{
    public void Do(Action action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        Try<object, Exception>(() => {
            action();
            return null;
        }, interval, retries);
    }

    public T Do<T>(Func<T> action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        return Try<T, Exception>(
              action
            , interval
            , retries);
    }

    public T Do<E, T>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        return Try<T, E>(
              action
            , interval
            , retries);
    }

    public void Do<E>(Action action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        Try<object, E>(() => {
            action();
            return null;
        }, interval, retries);
    }

    private T Try<T, E>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        var exceptions = new List<E>();

        for (int retry = 0; retry < retries; retry++)
        {
            try
            {
                if (retry > 0)
                    Thread.Sleep(interval);
                return action();
            }
            catch (E ex)
            {
                exceptions.Add(ex);
            }
        }

        throw new AggregateException(exceptions);
    }
}

2

Avevo bisogno di un metodo che supporti la cancellazione, mentre ero lì, ho aggiunto il supporto per la restituzione di errori intermedi.

public static class ThreadUtils
{
    public static RetryResult Retry(
        Action target,
        CancellationToken cancellationToken,
        int timeout = 5000,
        int retries = 0)
    {
        CheckRetryParameters(timeout, retries)
        var failures = new List<Exception>();
        while(!cancellationToken.IsCancellationRequested)
        {
            try
            {
                target();
                return new RetryResult(failures);
            }
            catch (Exception ex)
            {
                failures.Add(ex);
            }

            if (retries > 0)
            {
                retries--;
                if (retries == 0)
                {
                    throw new AggregateException(
                     "Retry limit reached, see InnerExceptions for details.",
                     failures);
                }
            }

            if (cancellationToken.WaitHandle.WaitOne(timeout))
            {
                break;
            }
        }

        failures.Add(new OperationCancelledException(
            "The Retry Operation was cancelled."));
        throw new AggregateException("Retry was cancelled.", failures);
    }

    private static void CheckRetryParameters(int timeout, int retries)
    {
        if (timeout < 1)
        {
            throw new ArgumentOutOfRangeException(...
        }

        if (retries < 0)
        {
            throw new ArgumentOutOfRangeException(...

        }
    }

    public class RetryResult : IEnumerable<Exception>
    {
        private readonly IEnumerable<Exception> failureExceptions;
        private readonly int failureCount;

         protected internal RetryResult(
             ICollection<Exception> failureExceptions)
         {
             this.failureExceptions = failureExceptions;
             this.failureCount = failureExceptions.Count;
         }
    }

    public int FailureCount
    {
        get { return this.failureCount; }
    }

    public IEnumerator<Exception> GetEnumerator()
    {
        return this.failureExceptions.GetEnumerator();
    }

    System.Collections.IEnumerator 
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

È possibile utilizzare la Retryfunzione in questo modo, riprovare 3 volte con un ritardo di 10 secondi ma senza annullamento.

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        CancellationToken.None,
        10000,
        3);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // oops, 3 retries wasn't enough.
}

Oppure, riprova per l'eternità ogni cinque secondi, a meno che non venga annullato.

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        someTokenSource.Token);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // operation was cancelled before success.
}

Come puoi immaginare, nel mio codice sorgente ho sovraccaricato la Retryfunzione per supportare i diversi tipi di delgate che desidero utilizzare.


2

Questo metodo consente di riprovare su alcuni tipi di eccezione (ne lancia immediatamente altri).

public static void DoRetry(
    List<Type> retryOnExceptionTypes,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
{
    for (var i = 0; i < retryCount; ++i)
    {
        try
        {
            actionToTry();
            break;
        }
        catch (Exception ex)
        {
            // Retries exceeded
            // Throws on last iteration of loop
            if (i == retryCount - 1) throw;

            // Is type retryable?
            var exceptionType = ex.GetType();
            if (!retryOnExceptionTypes.Contains(exceptionType))
            {
                throw;
            }

            // Wait before retry
            Thread.Sleep(msWaitBeforeEachRety);
        }
    }
}
public static void DoRetry(
    Type retryOnExceptionType,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
        => DoRetry(new List<Type> {retryOnExceptionType}, actionToTry, retryCount, msWaitBeforeEachRety);

Esempio di utilizzo:

DoRetry(typeof(IOException), () => {
    using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(entryBytes, 0, entryBytes.Length);
    }
});

1

Aggiornamento dopo 6 anni: ora ritengo che l'approccio che segue sia piuttosto negativo. Per creare una logica di nuovo tentativo dovremmo considerare di usare una libreria come Polly.


La mia asyncimplementazione del metodo retry:

public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
    {
        var exceptions = new List<Exception>();

        for (int retry = 0; retry < retryCount; retry++)
        {
            try
            {
                return await action().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }

            await Task.Delay(retryInterval).ConfigureAwait(false);
        }
        throw new AggregateException(exceptions);
    }

Punti chiave: ho usato .ConfigureAwait(false);e Func<dynamic>inveceFunc<T>


Questo non fornisce una risposta alla domanda. Considera di pubblicare la tua risposta come una nuova domanda, utilizzando il pulsante "Poni domanda" nella parte superiore della pagina, quindi pubblica la tua risposta alla domanda per condividere ciò che hai imparato con la community.
elixenide

Molto più semplice con C # 5.0 rispetto a codereview.stackexchange.com/q/55983/54000 ma forse CansellactionToken dovrebbe essere iniettato.
SerG,

C'è un problema con questa implementazione. Dopo l'ultimo tentativo, subito prima di arrendersi, Task.Delayviene chiamato senza motivo.
HappyNomad,

@HappyNomad questa è una risposta di 6 anni e ora considero un approccio piuttosto brutto per creare una logica di nuovo tentativo :)) grazie per la notifica. Aggiornerò la mia risposta in base a tale considerazione.
Cihan Uygun,

0

O che ne dici di farlo un po 'più ordinato ...

int retries = 3;
while (retries > 0)
{
  if (DoSomething())
  {
    retries = 0;
  }
  else
  {
    retries--;
  }
}

Credo che il lancio di eccezioni dovrebbe essere generalmente evitato come meccanismo a meno che tu non li passi oltre i confini (come la costruzione di una biblioteca che altre persone possono usare). Perché non solo DoSomething()restituire il comando truese ha avuto successo e in caso falsecontrario?

EDIT: E questo può essere incapsulato all'interno di una funzione come altri hanno suggerito anche. L'unico problema è se non si scrive la DoSomething()funzione da soli


7
"Credo che il lancio di eccezioni dovrebbe essere generalmente evitato come meccanismo a meno che tu non li passi oltre i confini" - Non sono completamente d'accordo. Come fai a sapere se il chiamante ha verificato il tuo reso falso (o peggio, nullo)? PERCHÉ il codice non è riuscito? False non ti dice nient'altro. Cosa succede se il chiamante deve superare l'errore nello stack? Leggi msdn.microsoft.com/en-us/library/ms229014.aspx - questi sono per le librerie, ma hanno lo stesso significato per il codice interno. E in una squadra, è probabile che altre persone chiamino il tuo codice.
TrueWill,

0

Avevo la necessità di passare alcuni parametri al mio metodo per riprovare e avere un valore risultato; quindi ho bisogno di un'espressione .. Ho creato questa classe che fa il lavoro (è ispirata a quella di LBushkin) puoi usarla in questo modo:

static void Main(string[] args)
{
    // one shot
    var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);

    // delayed execute
    var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
    var res2 = retry.Execute();
}

static void fix()
{
    Console.WriteLine("oh, no! Fix and retry!!!");
}

static string retryThis(string tryThis)
{
    Console.WriteLine("Let's try!!!");
    throw new Exception(tryThis);
}

public class Retry<TResult>
{
    Expression<Func<TResult>> _Method;
    int _NumRetries;
    TimeSpan _RetryTimeout;
    Action _OnFailureAction;

    public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
    {
        _Method = method;
        _NumRetries = numRetries;
        _OnFailureAction = onFailureAction;
        _RetryTimeout = retryTimeout;
    }

    public TResult Execute()
    {
        TResult result = default(TResult);
        while (_NumRetries > 0)
        {
            try
            {
                result = _Method.Compile()();
                break;
            }
            catch
            {
                _OnFailureAction();
                _NumRetries--;
                if (_NumRetries <= 0) throw; // improved to avoid silent failure
                Thread.Sleep(_RetryTimeout);
            }
        }
        return result;
    }

    public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
    {
        var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
        return retry.Execute();
    }
}

ps. la soluzione di LBushkin esegue un altro tentativo = D


0

Aggiungerei il seguente codice alla risposta accettata

public static class Retry<TException> where TException : Exception //ability to pass the exception type
    {
        //same code as the accepted answer ....

        public static T Do<T>(Func<T> action, TimeSpan retryInterval, int retryCount = 3)
        {
            var exceptions = new List<Exception>();

            for (int retry = 0; retry < retryCount; retry++)
            {
                try
                {
                    return action();
                }
                catch (TException ex) //Usage of the exception type
                {
                    exceptions.Add(ex);
                    Thread.Sleep(retryInterval);
                }
            }

            throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
        }
    }

Fondamentalmente il codice sopra sta facendo il Retry classe generica in modo da poter passare il tipo di eccezione che si desidera catturare per riprovare.

Ora usalo quasi allo stesso modo ma specificando il tipo di eccezione

Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));

Il ciclo for verrà sempre eseguito un paio di volte (in base a retryCount) anche se il codice nel ciclo TRY CATCH è stato eseguito senza eccezioni. Suggerirei di impostare retryCount uguale alla variante retry nel ciclo try, quindi il ciclo for smetterà di superarlo.
scre_www,

@scre_www Credo che ti sbagli. Se actionnon lancia, Doritorna quindi breakin modo da allontanarsi dal forloop.
HappyNomad,

In ogni caso, c'è un problema con questa implementazione. Dopo l'ultimo tentativo, subito prima di arrendersi, Thread.Sleepviene chiamato senza motivo.
HappyNomad,

0

So che questa risposta è molto antica, ma volevo solo commentare questo perché ho riscontrato problemi usando questi mentre, fai, qualunque affermazione con i contatori.

Nel corso degli anni ho optato per un approccio migliore, penso. Vale a dire usare una sorta di aggregazione di eventi come estensioni reattive "Soggetto" o simili. Quando un tentativo fallisce, devi semplicemente pubblicare un evento che dice che il tentativo è fallito e che la funzione aggregatore riprogrammi l'evento. Ciò consente un maggiore controllo sulla ripetizione senza inquinare la chiamata stessa con una serie di loop di ripetizione e cosa no. Né stai legando un singolo thread con un sacco di sleeps thread.


0

Fallo semplice in C #, Java o altre lingue:

  internal class ShouldRetryHandler {
    private static int RETRIES_MAX_NUMBER = 3;
    private static int numberTryes;

    public static bool shouldRetry() {
        var statusRetry = false;

        if (numberTryes< RETRIES_MAX_NUMBER) {
            numberTryes++;
            statusRetry = true;
            //log msg -> 'retry number' + numberTryes

        }

        else {
            statusRetry = false;
            //log msg -> 'reached retry number limit' 
        }

        return statusRetry;
    }
}

e usalo nel tuo codice molto semplice:

 void simpleMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    //do some repetitive work
     }

    //some code    
    }

oppure puoi usarlo in metodi ricorsivi:

void recursiveMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }

    //some code    
    }

0
int retries = 3;
while (true)
{
    try
    {
        //Do Somthing
        break;
    }
    catch (Exception ex)
    {
        if (--retries == 0)
            return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
        else
            System.Threading.Thread.Sleep(100);
    }

Che cosa fai Request.BadRequest?
Danh,

0

Riprova helper: un'implementazione java generica che contiene sia tentativi restituibili che vuoti.

import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryHelper {
  private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
  private int retryWaitInMS;
  private int maxRetries;

  public RetryHelper() {
    this.retryWaitInMS = 300;
    this.maxRetries = 3;
  }

  public RetryHelper(int maxRetry) {
    this.maxRetries = maxRetry;
    this.retryWaitInMS = 300;
  }

  public RetryHelper(int retryWaitInSeconds, int maxRetry) {
    this.retryWaitInMS = retryWaitInSeconds;
    this.maxRetries = maxRetry;
  }

  public <T> T retryAndReturn(Supplier<T> supplier) {
    try {
      return supplier.get();
    } catch (Exception var3) {
      return this.retrySupplier(supplier);
    }
  }

  public void retry(Runnable runnable) {
    try {
      runnable.run();
    } catch (Exception var3) {
      this.retrySupplier(() -> {
        runnable.run();
        return null;
      });
    }

  }

  private <T> T retrySupplier(Supplier<T> supplier) {
    log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
    int retryCounter = 0;

    while(retryCounter < this.maxRetries) {
      try {
        return supplier.get();
      } catch (Exception var6) {
        ++retryCounter;
        log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
        if (retryCounter >= this.maxRetries) {
          log.error("Max retries exceeded.");
          throw var6;
        }

        try {
          Thread.sleep((long)this.retryWaitInMS);
        } catch (InterruptedException var5) {
          var5.printStackTrace();
        }
      }
    }

    return supplier.get();
  }

  public int getRetryWaitInMS() {
    return this.retryWaitInMS;
  }

  public int getMaxRetries() {
    return this.maxRetries;
  }
}

Uso:

    try {
      returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
      //or no return type:
      new RetryHelper().retry(() -> mytask(args));
    } catch(Exception ex){
      log.error(e.getMessage());
      throw new CustomException();
    }

0

Ecco una versione async/ awaitche aggrega le eccezioni e supporta la cancellazione.

/// <seealso href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry"/>
protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
{
    var exceptions = new List<Exception>();

    for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
        try {
            return await action().ConfigureAwait( false );
        } catch ( Exception ex ) {
            exceptions.Add( ex );

            if ( retries < maxRetries )
                await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
            else
                throw new AggregateException( "Retry limit reached", exceptions );
        }

    exceptions.Add( new OperationCanceledException( cancelToken ) );
    throw new AggregateException( "Retry loop was canceled", exceptions );
}

-1
public delegate void ThingToTryDeletage();

public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime)
{
   while(true)
   {
      try
      {
        ThingToTryDelegate();
      } catch {

            if( --N == 0) throw;
          else Thread.Sleep(time);          
      }
}

Poiché throw;è l'unico modo per terminare il ciclo infinito, questo metodo sta implementando "prova fino a quando non fallisce N volte" e non il desiderato "prova fino a Nvolte finché non riesce". Hai bisogno di una break;o return;dopo la chiamata, ThingToTryDelegate();altrimenti verrà chiamata continuamente se non fallisce mai. Inoltre, questo non verrà compilato perché il primo parametro di TryNTimesnon ha nome. -1.
BACON

-1

Ho scritto una piccola classe basata sulle risposte postate qui. Spero che possa aiutare qualcuno: https://github.com/natenho/resiliency

using System;
using System.Threading;

/// <summary>
/// Classe utilitária para suporte a resiliência
/// </summary>
public sealed class Resiliency
{
    /// <summary>
    /// Define o valor padrão de número de tentativas
    /// </summary>
    public static int DefaultRetryCount { get; set; }

    /// <summary>
    /// Define o valor padrão (em segundos) de tempo de espera entre tentativas
    /// </summary>
    public static int DefaultRetryTimeout { get; set; }

    /// <summary>
    /// Inicia a parte estática da resiliência, com os valores padrões
    /// </summary>
    static Resiliency()
    {
        DefaultRetryCount = 3;
        DefaultRetryTimeout = 0;
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes  quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
    public static void Try(Action action)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
    {
        Try<Exception>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, retryCount, retryTimeout, tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action) where TException : Exception
    {
        Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    public static void Try<TException>(Action action, int retryCount) where TException : Exception
    {
        Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    /// <param name="retryTimeout"></param>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
    {
        Try<TException>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        while (retryCount-- > 0)
        {
            try
            {
                action();
                return;
            }
            catch (TException ex)
            {
                //Executa o manipulador de exception
                if (tryHandler != null)
                {
                    var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
                    tryHandler(callback);
                    //A propriedade que aborta pode ser alterada pelo cliente
                    if (callback.AbortRetry)
                        throw;
                }

                //Aguarda o tempo especificado antes de tentar novamente
                Thread.Sleep(retryTimeout);
            }
        }

        //Na última tentativa, qualquer exception será lançada de volta ao chamador
        action();
    }

}

/// <summary>
/// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
/// </summary>
public class ResiliencyTryHandler<TException> where TException : Exception
{
    #region Properties

    /// <summary>
    /// Opção para abortar o ciclo de tentativas
    /// </summary>
    public bool AbortRetry { get; set; }

    /// <summary>
    /// <see cref="Exception"/> a ser tratada
    /// </summary>
    public TException Exception { get; private set; }

    /// <summary>
    /// Identifca o número da tentativa atual
    /// </summary>
    public int CurrentTry { get; private set; }

    #endregion

    #region Constructors

    /// <summary>
    /// Instancia um manipulador de tentativa. É utilizado internamente
    /// por <see cref="Resiliency"/> para permitir que o cliente altere o
    /// comportamento do ciclo de tentativas
    /// </summary>
    public ResiliencyTryHandler(TException exception, int currentTry)
    {
        Exception = exception;
        CurrentTry = currentTry;
    }

    #endregion

}

-1

Ho implementato una versione asincrona della risposta accettata in questo modo - e sembra funzionare bene - qualche commento?


        public static async Task DoAsync(
            Action action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            DoAsync<object>(() =>
            {
                action();
                return null;
            }, retryInterval, maxAttemptCount);
        }

        public static async Task<T> DoAsync<T>(
            Func<Task<T>> action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            var exceptions = new List<Exception>();

            for (int attempted = 0; attempted < maxAttemptCount; attempted++)
            {
                try
                {
                    if (attempted > 0)
                    {
                        Thread.Sleep(retryInterval);
                    }
                    return await action();
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            throw new AggregateException(exceptions);
        }

E chiamalo semplicemente così:

var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);

Thread.Sleep? Il blocco di un thread annulla i benefici dell'asincronia. Inoltre sono abbastanza sicuro che la Task DoAsync()versione dovrebbe accettare un argomento di tipo Func<Task>.
Theodor Zoulias,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.