Der sauberste Weg, um Wiederholungslogik zu schreiben?


455

Gelegentlich muss ich eine Operation mehrmals wiederholen, bevor ich aufgebe. Mein Code lautet wie folgt:

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

Ich möchte dies in einer allgemeinen Wiederholungsfunktion wie folgt umschreiben:

TryThreeTimes(DoSomething);

Ist es in C # möglich? Was wäre der Code für die TryThreeTimes()Methode?


1
Ein einfacher Zyklus reicht nicht aus? Warum nicht einfach mehrmals iterieren und Logik ausführen?
Restuta

13
Persönlich wäre ich bei solchen Hilfsmethoden äußerst vorsichtig. Es ist sicherlich möglich, Lambdas zu implementieren, aber das Muster selbst stinkt extrem, so dass die Einführung eines Helfers (was impliziert, dass es häufig wiederholt wird) an und für sich sehr verdächtig ist und stark auf ein schlechtes Gesamtdesign hinweist.
Pavel Minaev

12
In meinem Fall erledigen meine DoSomething () Dinge auf Remotecomputern, z. B. das Löschen von Dateien oder den Versuch, einen Netzwerkport zu erreichen. In beiden Fällen gibt es große zeitliche Probleme, wenn DoSomething erfolgreich sein wird, und aufgrund der Abgeschiedenheit gibt es kein Ereignis, das ich abhören kann. Also ja, es stinkt. Vorschläge willkommen.
Noctonura

13
@PavelMinaev warum sollte die Verwendung von Wiederholungsversuchen auf ein schlechtes Gesamtdesign hinweisen? Wenn Sie viel Code schreiben, der Integrationspunkte verbindet, ist die Verwendung von Wiederholungsversuchen definitiv ein Muster, das Sie ernsthaft in Betracht ziehen sollten.
Bytedev

Antworten:


569

Pauschal-Catch-Anweisungen, die denselben Aufruf einfach wiederholen, können gefährlich sein, wenn sie als allgemeiner Mechanismus zur Behandlung von Ausnahmen verwendet werden. Trotzdem ist hier ein Lambda-basierter Wiederholungs-Wrapper, den Sie mit jeder Methode verwenden können. Ich habe mich dafür entschieden, die Anzahl der Wiederholungsversuche und das Zeitlimit für Wiederholungsversuche als Parameter für etwas mehr Flexibilität zu berücksichtigen:

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);
    }
}

Mit dieser Dienstprogrammmethode können Sie jetzt eine Wiederholungslogik ausführen:

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

oder:

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

oder:

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

Oder Sie könnten sogar eine asyncÜberlastung machen.


7
+1, insbesondere für die Warnung und Fehlerprüfung. Ich würde mich wohler fühlen, wenn dies in der Art der Ausnahme übergeben würde, die als generischer Parameter abgefangen werden soll (wobei T: Ausnahme).
TrueWill

1
Es war meine Absicht, dass "Wiederholungsversuche" tatsächlich Wiederholungsversuche bedeuteten. Aber es ist nicht allzu schwer, es so zu ändern, dass es "Versuche" bedeutet. Solange der Name aussagekräftig bleibt. Es gibt andere Möglichkeiten, den Code zu verbessern, z. B. das Überprüfen auf negative Wiederholungsversuche oder negative Zeitüberschreitungen. Ich habe diese meistens weggelassen, um das Beispiel einfach zu halten ... aber in der Praxis wären dies wahrscheinlich gute Verbesserungen der Implementierung.
LBushkin

40
Wir verwenden ein ähnliches Muster für unseren DB-Zugriff in einer Biztalk-App mit hohem Volumen, jedoch mit zwei Verbesserungen: Wir haben Blacklists für Ausnahmen, die nicht wiederholt werden sollten, und wir speichern die erste auftretende Ausnahme und lösen diese aus, wenn die Wiederholung letztendlich fehlschlägt. Grund dafür ist, dass sich die zweite und die folgenden Ausnahmen häufig von der ersten unterscheiden. In diesem Fall verbergen Sie das anfängliche Problem, wenn Sie nur die letzte Ausnahme erneut auslösen.
TToni

2
@Dexters Wir lösen eine neue Ausnahme mit der ursprünglichen Ausnahme als innere Ausnahme aus. Die ursprüngliche Stapelverfolgung ist als Attribut aus den inneren Ausnahmen verfügbar.
TToni

7
Sie können auch versuchen, eine Open Source-Bibliothek wie Polly zu verwenden, um dies zu handhaben. Es gibt viel mehr Flexibilität beim Warten zwischen Wiederholungsversuchen und es wurde von vielen anderen validiert, die das Projekt verwendet haben. Beispiel: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen

222

Sie sollten Polly versuchen . Es handelt sich um eine von mir geschriebene .NET-Bibliothek, mit der Entwickler Richtlinien für die vorübergehende Ausnahmebehandlung wie "Wiederholen", "Für immer wiederholen", "Warten und erneut versuchen" oder "Leistungsschalter" fließend ausdrücken können.

Beispiel

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

1
Was ist eigentlich der OnRetry-Delegierte? Ich gehe davon aus, dass wir dies tun müssen, wenn eine Ausnahme auftritt. Wenn also eine Ausnahme auftritt, ruft der OnRetry-Delegat auf und führt anschließend den Delegaten aus. Ist es so?
user6395764

61

Dies ist möglicherweise eine schlechte Idee. Erstens ist es ein Symbol für die Maxime "Die Definition von Wahnsinn macht zweimal dasselbe und erwartet jedes Mal andere Ergebnisse". Zweitens passt dieses Codierungsmuster nicht gut zu sich selbst. Zum Beispiel:

Angenommen, Ihre Netzwerkhardwareschicht sendet ein Paket bei einem Fehler dreimal erneut und wartet beispielsweise eine Sekunde zwischen den Fehlern.

Angenommen, die Softwareschicht sendet eine Benachrichtigung über einen Fehler dreimal bei einem Paketfehler erneut.

Angenommen, die Benachrichtigungsschicht reaktiviert die Benachrichtigung dreimal bei einem Fehler bei der Zustellung der Benachrichtigung.

Angenommen, die Fehlerberichtsschicht reaktiviert die Benachrichtigungsschicht dreimal bei einem Benachrichtigungsfehler.

Angenommen, der Webserver reaktiviert die Fehlerberichterstattung dreimal bei einem Fehler.

Angenommen, der Webclient sendet die Anforderung dreimal erneut, nachdem er einen Fehler vom Server erhalten hat.

Angenommen, die Leitung auf dem Netzwerk-Switch, die die Benachrichtigung an den Administrator weiterleiten soll, ist nicht angeschlossen. Wann erhält der Benutzer des Webclients endlich seine Fehlermeldung? Ich schaffe es ungefähr zwölf Minuten später.

Damit Sie nicht glauben, dass dies nur ein dummes Beispiel ist: Wir haben diesen Fehler im Kundencode gesehen, obwohl er weitaus schlimmer ist, als ich hier beschrieben habe. In dem bestimmten Kundencode betrug die Lücke zwischen dem Auftreten des Fehlerzustands und der endgültigen Meldung an den Benutzer mehrere Wochen, da so viele Ebenen automatisch mit Wartezeiten erneut versucht wurden. Stellen Sie sich vor, was passieren würde, wenn zehn statt drei Wiederholungen durchgeführt würden .

Normalerweise ist es das Richtige, eine Fehlerbedingung sofort zu melden und den Benutzer entscheiden zu lassen, was zu tun ist. Wenn der Benutzer eine Richtlinie für automatische Wiederholungsversuche erstellen möchte, lassen Sie ihn diese Richtlinie auf der entsprechenden Ebene in der Software-Abstraktion erstellen.


19
+1. Raymond gibt hier ein Beispiel aus dem wirklichen Leben: blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi

210
-1 Dieser Hinweis ist für vorübergehende Netzwerkfehler bei automatisierten Stapelverarbeitungssystemen unbrauchbar.
nichts

15
Ich bin mir nicht sicher, ob dies "Tu es nicht" sagt, gefolgt von "Tu es". Die meisten Leute, die diese Frage stellen, sind wahrscheinlich die Leute, die in der Software-Abstraktion arbeiten.
Jim L

44
Wenn Sie lange laufende Batch-Jobs haben, die Netzwerkressourcen wie Webdienste verwenden, können Sie nicht erwarten, dass das Netzwerk 100% zuverlässig ist. Es kommt gelegentlich zu Zeitüberschreitungen, Socket-Trennungen, möglicherweise sogar zu falschen Routing-Störungen oder Serverausfällen, die während der Verwendung auftreten. Eine Möglichkeit besteht darin, fehlzuschlagen. Dies kann jedoch bedeuten, dass ein längerer Job später neu gestartet wird. Eine andere Möglichkeit besteht darin, einige Male mit einer geeigneten Verzögerung erneut zu versuchen, um festzustellen, ob es sich um ein vorübergehendes Problem handelt, und dann fehlzuschlagen. Ich stimme der Komposition zu, die Sie beachten müssen. Aber manchmal ist sie die beste Wahl.
Erik Funkenbusch

18
Ich denke, dass das Zitat, das Sie zu Beginn Ihrer Antwort verwendet haben, interessant ist. "Unterschiedliche Ergebnisse erwarten" ist nur dann Wahnsinn, wenn Sie aufgrund früherer Erfahrungen regelmäßig dieselben Ergebnisse erzielen. Während Software auf dem Versprechen der Konsistenz basiert, gibt es definitiv Umstände, unter denen wir mit unzuverlässigen Kräften interagieren müssen, die außerhalb unserer Kontrolle liegen.
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);
        }
    }
}

Dann würden Sie anrufen:

TryThreeTimes(DoSomething);

...oder alternativ...

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

Eine flexiblere Option:

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);
        }
   }
}

Zu verwenden als:

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

Eine modernere Version mit Unterstützung für 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);
        }
   }
}

Zu verwenden als:

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

2
Ändern Sie das if vorzugsweise in: --retryCount <= 0weil dies für immer so bleibt, wenn Sie Wiederholungsversuche deaktivieren möchten, indem Sie es auf 0 setzen. Technisch gesehen ist der Begriff retryCountkein wirklich guter Name, da er nicht wiederholt wird, wenn Sie ihn auf 1 setzen es zu tryCountoder setzen die - hinter.
Stefanvds

2
@saille Ich stimme zu. Das OP (und alle anderen Antworten) verwenden jedoch Thread.Sleep. Alternativen sind die Verwendung von Timern oder heutzutage eher die asyncWiederholung von Task.Delay.
Drew Noakes

2
Ich habe eine asynchrone Version hinzugefügt.
Drew Noakes

Nur brechen, wenn Aktion returns true? Func<bool>
Kiquenet

32

Der Anwendungsblock für die vorübergehende Fehlerbehandlung bietet eine erweiterbare Sammlung von Wiederholungsstrategien, darunter:

  • Inkrementell
  • Festes Intervall
  • Exponentieller Rückzieher

Es enthält auch eine Sammlung von Fehlererkennungsstrategien für Cloud-basierte Dienste.

Weitere Informationen finden Sie in diesem Kapitel des Entwicklerhandbuchs.

Erhältlich über NuGet (Suche nach ' Topas ').


1
Interessant. Können Sie dies außerhalb von Windows Azure verwenden, beispielsweise in einer Winforms-App?
Matthew Lock

6
Absolut. Verwenden Sie den Kernwiederholungsmechanismus und stellen Sie Ihre eigenen Erkennungsstrategien bereit. Diese haben wir absichtlich entkoppelt. Das Kern-Nuget-Paket finden Sie hier: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik

2
Außerdem befindet sich das Projekt jetzt unter Apache 2.0 und akzeptiert Community-Beiträge. aka.ms/entlibopen
Grigori Melnik

1
@Alex. Die Teile davon schaffen es in die Plattform.
Grigori Melnik

2
Dies ist jetzt veraltet und zuletzt habe ich es verwendet. Es enthielt einige Fehler, die meines Wissens nicht behoben wurden und niemals behoben werden: github.com/MicrosoftArchive/… .
Ohad Schneider

15

Funktionen berücksichtigen und Meldungen wiederholen

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 sind True odermax retries?
Kiquenet

14

Sie können auch den Ausnahmetyp hinzufügen, für den Sie es erneut versuchen möchten. Ist dies beispielsweise eine Timeout-Ausnahme, die Sie erneut versuchen möchten? Eine Datenbankausnahme?

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);
        }
    }
}

Sie können auch feststellen, dass alle anderen Beispiele ein ähnliches Problem beim Testen auf Wiederholungsversuche == 0 haben und entweder unendlich wiederholen oder Ausnahmen nicht auslösen, wenn ein negativer Wert angegeben wird. Auch Sleep (-1000) schlägt in den obigen Fangblöcken fehl. Kommt darauf an, wie "albern" man die Leute erwartet, aber defensives Programmieren tut nie weh.


9
+1, aber warum nicht RetryForException <T> (...) wo T: Exception, dann catch (T e)? Einfach mal ausprobiert und es funktioniert perfekt.
TrueWill

Entweder oder hier, da ich mit dem Typ nichts anfangen muss, vorausgesetzt, ich dachte, ein einfacher alter Parameter würde den Trick machen.
csharptest.net

@TrueWill anscheinend fangen (T ex) hat einige Fehler gemäß diesem Beitrag stackoverflow.com/questions/1577760/…
csharptest.net

3
Update: Für eine bessere Implementierung, die ich verwendet habe, ist ein Predicate <Exception> -Delegat erforderlich, der true zurückgibt, wenn ein erneuter Versuch angemessen ist. Auf diese Weise können Sie native Fehlercodes oder andere Eigenschaften der Ausnahme verwenden, um festzustellen, ob ein erneuter Versuch möglich ist. Zum Beispiel HTTP 503-Codes.
csharptest.net

1
"Auch Sleep (-1000) schlägt in den obigen Catch-Blöcken fehl" ... Verwenden Sie eine Zeitspanne, und Sie werden dieses Problem nicht bekommen. Außerdem ist TimeSpan viel flexibler und selbstbeschreibender. Woher weiß ich aus Ihrer Signatur von "int retryTimeout", ob retryTimeout MS, Sekunden, Minuten, Jahre ist? ;-)
bytedev

13

Ich bin ein Fan von Rekursions- und Erweiterungsmethoden. Hier sind meine zwei Cent:

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

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

7

Aufbauend auf der vorherigen Arbeit habe ich darüber nachgedacht, die Wiederholungslogik auf drei Arten zu verbessern:

  1. Angeben, welcher Ausnahmetyp abgefangen / erneut versucht werden soll. Dies ist die primäre Verbesserung, da ein erneuter Versuch für eine Ausnahme einfach falsch ist.
  2. Den letzten Versuch nicht in einen Versuch / Fang einbetten, um eine etwas bessere Leistung zu erzielen
  3. Machen Sie es zu einer ActionErweiterungsmethode

    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( );
      }
    }

Die Methode kann dann wie folgt aufgerufen werden (natürlich können auch anonyme Methoden verwendet werden):

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

1
Das ist ausgezeichnet. Aber ich persönlich würde es nicht "retryTimeout" nennen, da es nicht wirklich ein Timeout ist. Vielleicht 'RetryDelay'?
Holf

7

Halten Sie es einfach mit 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
Ich bin ein bisschen neugierig, würde dies eine verrückte Menge von Threads mit einer hohen Anzahl von Wiederholungsversuchen und Intervallen erzeugen, weil dieselbe erwartete Methode zurückgegeben wird?
HuntK24

6

Verwenden Sie Polly

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

Hier ist ein Wiederholungs-Generikum, das ich mit Polly verwende

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;
}

Verwenden Sie es so

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

5

Die Antwort von LBushkin wurde auf die neueste Weise umgesetzt:

    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);
    }  

und um es zu benutzen:

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

wohingegen die Funktion [TaskFunction]entweder Task<T>oder nur sein kann Task.


1
Danke, Fabian! Dies sollte ganz nach oben gestimmt werden!
JamesHoux

1
@ MarkLauter die kurze Antwort ist ja. ;-)
Fabian Bigler

4

Ich würde dies implementieren:

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

Ich würde Ausnahmen nicht so verwenden, wie sie in den anderen Beispielen verwendet werden. Es scheint mir, dass wenn wir die Möglichkeit erwarten, dass eine Methode nicht erfolgreich sein wird, ihr Fehler keine Ausnahme ist. Die Methode, die ich aufrufe, sollte also true zurückgeben, wenn sie erfolgreich war, und false, wenn sie fehlgeschlagen ist.

Warum ist es ein Func<bool, bool>und nicht nur ein Func<bool>? Also das, wenn ich will eine Methode bei einem Fehler eine Ausnahme auslösen kann, kann ich sie darüber informieren, dass dies der letzte Versuch ist.

Also könnte ich es mit Code verwenden wie:

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

oder

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

Wenn sich die Übergabe eines Parameters, den die Methode nicht verwendet, als umständlich herausstellt, ist es trivial, eine Überladung zu implementieren, für Retrydie nur ein erforderlich ist Func<bool>.


1
+1 zur Vermeidung der Ausnahme. Obwohl ich einen leeren Wiederholungsversuch machen würde (...) und etwas werfen würde? Boolesche Rückgaben und / oder Rückgabecodes werden zu oft übersehen.
csharptest.net

1
"Wenn wir die Möglichkeit erwarten, dass eine Methode nicht erfolgreich ist, ist ihr Fehler keine Ausnahme" - obwohl dies in einigen Fällen zutrifft, muss eine Ausnahme keine Ausnahme bedeuten. Es ist für die Fehlerbehandlung. Es gibt keine Garantie dafür, dass der Anrufer ein boolesches Ergebnis überprüft. Es besteht die Garantie, dass eine Ausnahme behandelt wird (durch das Herunterfahren der Anwendung zur Laufzeit, wenn nichts anderes der Fall ist).
TrueWill

Ich kann die Referenz nicht finden, aber ich glaube, .NET definiert eine Ausnahme als "eine Methode hat nicht das getan, was sie versprochen hat". Ein Zweck besteht darin, Ausnahmen zu verwenden, um auf ein Problem hinzuweisen, und nicht auf das Win32-Muster, wonach der Aufrufer den Rückgabewert überprüfen muss, ob die Funktion erfolgreich war oder nicht.
Noctonura

Ausnahmen weisen jedoch nicht nur auf ein Problem hin. Sie enthalten auch eine Vielzahl von Diagnoseinformationen, deren Kompilierung Zeit und Speicher kostet. Es gibt eindeutig Situationen, in denen das keine Rolle spielt. Aber es gibt viele, wo es tut. .NET verwendet keine Ausnahmen für den Kontrollfluss (vergleiche beispielsweise die Verwendung der StopIterationAusnahme durch Python ), und es gibt einen Grund.
Robert Rossney

Das TryDoMethodenmuster ist eine rutschige Steigung. Bevor Sie es wissen, besteht Ihr gesamter Aufrufstapel aus TryDoMethoden. Ausnahmen wurden erfunden, um ein solches Durcheinander zu vermeiden.
HappyNomad

2

Exponentielles Backoff ist eine gute Wiederholungsstrategie, als es einfach x-mal zu versuchen. Sie können eine Bibliothek wie Polly verwenden, um sie zu implementieren.


2

Verwenden Sie Folgendes, wenn Sie beide Optionen haben möchten, um eine Ausnahme erneut zu versuchen oder den Ausnahmetyp explizit festzulegen.

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

Ich brauchte eine Methode, die das Abbrechen unterstützt. Während ich dabei war, habe ich Unterstützung für die Rückgabe von Zwischenfehlern hinzugefügt.

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();
    }
}

Sie können diese RetryFunktion wie folgt verwenden. Versuchen Sie es dreimal mit einer Verzögerung von 10 Sekunden, jedoch ohne Abbruch.

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.
}

Oder versuchen Sie es ewig alle fünf Sekunden, sofern nicht abgebrochen.

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.
}

Wie Sie Retrysich vorstellen können, habe ich in meinem Quellcode die Funktion überladen , um die unterschiedlichen Delgate-Typen zu unterstützen, die ich verwenden möchte.


2

Diese Methode ermöglicht Wiederholungsversuche für bestimmte Ausnahmetypen (löst andere sofort aus).

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);

Anwendungsbeispiel:

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

1

Update nach 6 Jahren: Jetzt halte ich den folgenden Ansatz für ziemlich schlecht. Um eine Wiederholungslogik zu erstellen, sollten Sie eine Bibliothek wie Polly verwenden.


Meine asyncImplementierung der Wiederholungsmethode:

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);
    }

Wichtige Punkte: Ich habe .ConfigureAwait(false);und Func<dynamic>stattdessen verwendetFunc<T>


Dies gibt keine Antwort auf die Frage. Bitte erwägen Sie, Ihre Antwort als neue Frage zu veröffentlichen. Verwenden Sie dazu die Schaltfläche "Frage stellen" oben auf der Seite und dann Ihre eigene Antwort auf die Frage, um der Community mitzuteilen, was Sie gelernt haben.
Elixenid

Mit C # 5.0 viel einfacher als mit codereview.stackexchange.com/q/55983/54000, aber möglicherweise sollte CansellactionToken injiziert werden.
SerG

Es gibt ein Problem mit dieser Implementierung. Nach der letzten Wiederholung, kurz vor dem Aufgeben, Task.Delaywird ohne Grund aufgerufen.
HappyNomad

@HappyNomad Dies ist eine 6 Jahre alte Antwort und jetzt halte ich es für einen ziemlich schlechten Ansatz, eine Wiederholungslogik zu erstellen :)) Danke für die Benachrichtigung. Ich werde meine Antwort entsprechend dieser Überlegung aktualisieren.
Cihan Uygun

0

Oder wie wäre es ein bisschen ordentlicher ...

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

Ich bin der Meinung, dass das Auslösen von Ausnahmen im Allgemeinen als Mechanismus vermieden werden sollte, es sei denn, Sie überschreiten sie zwischen Grenzen (z. B. beim Aufbau einer Bibliothek, die andere Personen verwenden können). Warum nicht einfach den DoSomething()Befehl zurückgeben lassen, truewenn er erfolgreich war und falsesonst?

BEARBEITEN: Und dies kann in eine Funktion eingekapselt werden, wie es auch andere vorgeschlagen haben. Das einzige Problem ist, wenn Sie die DoSomething()Funktion nicht selbst schreiben


7
"Ich glaube, das Werfen von Ausnahmen sollte generell als Mechanismus vermieden werden, es sei denn, Sie überschreiten sie zwischen Grenzen" - ich bin völlig anderer Meinung. Woher wissen Sie, dass der Anrufer Ihre falsche (oder schlimmer noch null) Rückgabe überprüft hat? WARUM ist der Code fehlgeschlagen? Falsch sagt dir nichts anderes. Was ist, wenn der Aufrufer den Fehler im Stapel weitergeben muss? Lesen Sie msdn.microsoft.com/en-us/library/ms229014.aspx - diese sind für Bibliotheken, aber für internen Code genauso sinnvoll. In einem Team rufen wahrscheinlich andere Personen Ihren Code an.
TrueWill

0

Ich musste einen Parameter an meine Methode übergeben, um es erneut zu versuchen, und einen Ergebniswert haben. Also brauche ich einen Ausdruck. Ich baue diese Klasse auf, die die Arbeit macht (sie ist inspiriert von der des LBushkin). Du kannst sie so verwenden:

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. Die LBushkin-Lösung führt einen weiteren Versuch durch = D.


0

Ich würde der akzeptierten Antwort den folgenden Code hinzufügen

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);
        }
    }

Grundsätzlich macht der obige Code die Retry Klasse generisch, sodass Sie den Typ der Ausnahme übergeben können, die Sie für einen erneuten Versuch abfangen möchten.

Verwenden Sie es jetzt fast genauso, geben Sie jedoch den Ausnahmetyp an

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

Die for-Schleife wird immer ein paar Mal ausgeführt (basierend auf Ihrem retryCount), auch wenn der Code in der TRY CATCH-Schleife ausnahmslos ausgeführt wurde. Ich würde vorschlagen, retryCount gleich dem retry var in der try-Schleife zu setzen, damit die for-Schleife nicht mehr darüber läuft.
scre_www

@scre_www Ich glaube, Sie irren sich. Wenn actionnicht geworfen wird, Dokehrt breakdie forSchleife zurück.
HappyNomad

In jedem Fall gibt es ein Problem mit dieser Implementierung. Nach der letzten Wiederholung, kurz vor dem Aufgeben, Thread.Sleepwird ohne Grund aufgerufen.
HappyNomad

0

Ich weiß, dass diese Antwort sehr alt ist, aber ich wollte dies nur kommentieren, weil ich bei der Verwendung dieser Probleme auf Probleme gestoßen bin, während ich eine Aussage mit Zählern mache.

Im Laufe der Jahre habe ich mich für einen besseren Ansatz entschieden, denke ich. Das heißt, eine Art Ereignisaggregation wie eine reaktive Erweiterung "Subject" oder dergleichen zu verwenden. Wenn ein Versuch fehlschlägt, veröffentlichen Sie einfach ein Ereignis, das besagt, dass der Versuch fehlgeschlagen ist, und lassen die Aggregatorfunktion das Ereignis neu planen. Dies ermöglicht Ihnen viel mehr Kontrolle über den Wiederholungsversuch, ohne den Anruf selbst mit einer Reihe von Wiederholungsschleifen zu verschmutzen und was nicht. Sie binden auch keinen einzelnen Thread mit einem Bündel von Thread-Schlafplätzen zusammen.


0

Machen Sie es einfach in C #, Java oder anderen Sprachen:

  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;
    }
}

und verwenden Sie es in Ihrem Code sehr einfach:

 void simpleMethod(){
    //some code

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

    //some code    
    }

oder Sie können es in rekursiven Methoden verwenden:

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);
    }

Was tun Sie mit Request.BadRequest?
Danh

0

Wiederholungs-Helfer: Eine generische Java-Implementierung, die sowohl wiederkehrbare als auch nichtige Wiederholungsversuche enthält.

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;
  }
}

Verwendungszweck:

    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

Hier ist eine async/ awaitVersion, die Ausnahmen aggregiert und die Stornierung unterstützt.

/// <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);          
      }
}

Da dies die throw;einzige Möglichkeit ist, die Endlosschleife zu beenden, implementiert diese Methode tatsächlich "Versuch, bis sie N-mal fehlschlägt" und nicht den gewünschten "Versuch bis zu NZeiten, bis sie erfolgreich ist". Sie benötigen ein break;oder return;nach dem Anruf, ThingToTryDelegate();sonst wird es kontinuierlich aufgerufen, wenn es nie fehlschlägt. Dies wird auch nicht kompiliert, da der erste Parameter von TryNTimeskeinen Namen hat. -1.
Speck

-1

Ich habe eine kleine Klasse geschrieben, die auf den hier veröffentlichten Antworten basiert. Hoffentlich hilft es jemandem: 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

Ich habe eine asynchrone Version der akzeptierten Antwort wie folgt implementiert - und es scheint gut zu funktionieren - irgendwelche Kommentare?


        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);
        }

Und nennen Sie es einfach so:

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

Thread.Sleep? Durch das Blockieren eines Threads werden die Vorteile der Asynchronität zunichte gemacht. Ich bin mir auch ziemlich sicher, dass die Task DoAsync()Version ein Argument vom Typ akzeptieren sollte Func<Task>.
Theodor Zoulias
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.