De schoonste manier om logica opnieuw te schrijven?

Af en toe moet ik een operatie meerdere keren opnieuw proberen voordat ik het opgeef. Mijn code is als:

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

Ik zou dit willen herschrijven in een algemene functie voor opnieuw proberen, zoals:

TryThreeTimes(DoSomething);

Is het mogelijk in C#? Wat zou de code zijn voor de methode TryThreeTimes()?


Antwoord 1, autoriteit 100%

Algemene catch-statements die dezelfde aanroep gewoon opnieuw proberen, kunnen gevaarlijk zijn als ze worden gebruikt als een algemeen mechanisme voor het afhandelen van uitzonderingen. Dat gezegd hebbende, hier is een op lambda gebaseerde retry-wrapper die je met elke methode kunt gebruiken. Ik heb ervoor gekozen om het aantal nieuwe pogingen en de time-out voor opnieuw proberen als parameters te gebruiken voor wat meer flexibiliteit:

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

U kunt nu deze hulpprogrammamethode gebruiken om logica voor opnieuw proberen uit te voeren:

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

of:

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

of:

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

Of je zou zelfs een asyncoverbelasting kunnen maken.


Antwoord 2, autoriteit 42%

Je moet Pollyproberen. Het is een .NET-bibliotheek die door mij is geschreven en waarmee ontwikkelaars op een vloeiende manier tijdelijke beleidsregels voor het afhandelen van uitzonderingen kunnen uitdrukken, zoals Retry, Retry Forever, Wait and Retry of Circuit Breaker.

Voorbeeld

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

Antwoord 3, autoriteit 11%

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

Dan bel je:

TryThreeTimes(DoSomething);

…of anders…

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

Een flexibelere optie:

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

Te gebruiken als:

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

Een modernere versie met ondersteuning voor async/wait:

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

Te gebruiken als:

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

Antwoord 4, autoriteit 10%

Dit is mogelijk een slecht idee. Ten eerste is het symbolisch voor de stelregel “de definitie van waanzin is twee keer hetzelfde doen en elke keer andere resultaten verwachten”. Ten tweede past dit coderingspatroon niet goed bij zichzelf. Bijvoorbeeld:

Stel dat uw netwerkhardwarelaag een pakket drie keer opnieuw verzendt bij een storing, bijvoorbeeld een seconde wachtend tussen storingen.

Stel nu dat de softwarelaag een melding over een fout drie keer opnieuw verzendt bij een pakketfout.

Stel nu dat de notificatielaag de notificatie drie keer opnieuw activeert bij een mislukte levering van notificaties.

Stel nu dat de foutrapportagelaag de meldingslaag drie keer opnieuw activeert bij een meldingsfout.

En stel nu dat de webserver de foutrapportage drie keer opnieuw activeert bij een foutfout.

En stel nu dat de webclient het verzoek drie keer opnieuw verzendt nadat hij een foutmelding van de server heeft gekregen.

Stel nu dat de lijn op de netwerkswitch die de melding naar de beheerder moet sturen, is losgekoppeld. Wanneer krijgt de gebruiker van de webclient eindelijk zijn foutmelding? Ik haal het ongeveer twaalf minuten later.

Tenzij je denkt dat dit slechts een dwaas voorbeeld is: we hebben deze bug in de klantcode gezien, hoewel veel, veel erger dan ik hier heb beschreven. In de specifieke klantcode was de kloof tussen het optreden van de fout en het uiteindelijk rapporteren aan de gebruiker enkele wekenomdat zoveel lagen automatisch opnieuw probeerden met wachttijden. Stelt u zich eens voor wat er zou gebeuren als er tiennieuwe pogingen waren in plaats van drie.

Meestal is het het juiste om te doen met een foutconditie het onmiddellijk te melden en de gebruiker te laten beslissen wat te doen.Als de gebruiker een beleid van automatische nieuwe pogingen wil maken, laat hem dan dat beleid maken op het juiste niveau in de software-abstractie.


Antwoord 5, autoriteit 5%

Het Toepassingsblok voor tijdelijke foutafhandelingbiedt een uitbreidbare verzameling strategieën voor opnieuw proberen, waaronder:

  • Incrementeel
  • Vast interval
  • Exponentiële terugval

Het bevat ook een verzameling foutdetectiestrategieën voor cloudgebaseerde services.

Voor meer informatie, zie dit hoofdstukvan de Handleiding voor ontwikkelaars.

Beschikbaar via NuGet(zoek op ‘topaz‘ ).


Antwoord 6, autoriteit 3%

Ik ben een fan van recursie- en extensiemethoden, dus hier zijn mijn twee cent:

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

Antwoord 7, autoriteit 2%

Functies toestaan ​​en berichten opnieuw proberen

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

Antwoord 8, autoriteit 2%

U kunt ook overwegen het uitzonderingstype toe te voegen waarvoor u het opnieuw wilt proberen. Is dit bijvoorbeeld een time-outuitzondering die u opnieuw wilt proberen? Een database-uitzondering?

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

Je zou ook kunnen opmerken dat alle andere voorbeelden een soortgelijk probleem hebben met testen op nieuwe pogingen == 0 en ofwel oneindig opnieuw proberen of geen uitzonderingen genereren wanneer ze een negatieve waarde krijgen. Ook zal Sleep (-1000) mislukken in de bovenstaande catch-blokken. Hangt ervan af hoe ‘dom’ je van mensen verwacht, maar defensief programmeren kan nooit kwaad.


Antwoord 9

Houd het simpel met 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);
    }
}

Antwoord 10

Gebruik Polly

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

Hier is een generiek voor opnieuw proberen die ik gebruik met 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;
}

Gebruik het zo

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

Antwoord 11

Voortbouwend op het vorige werk, dacht ik erover na om de logica voor opnieuw proberen op drie manieren te verbeteren:

  1. Opgeven welk type uitzondering moet worden opgevangen/opnieuw geprobeerd. Dit is de belangrijkste verbetering, omdat het opnieuw proberen voor een uitzondering gewoon verkeerd is.
  2. De laatste poging niet nesten in een try/catch, waardoor een iets betere prestatie wordt behaald
  3. Er een Actionextensiemethode van maken

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

De methode kan dan als volgt worden aangeroepen (anonieme methoden kunnen natuurlijk ook worden gebruikt):

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

Antwoord 12

Het antwoord van LBushkin op de laatste manier geïmplementeerd:

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

en om het te gebruiken:

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

terwijl de functie [TaskFunction]ofwel Task<T>of gewoon Taskkan zijn.


Antwoord 13

Ik zou dit implementeren:

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

Ik zou uitzonderingen niet gebruiken zoals ze in de andere voorbeelden worden gebruikt. Het lijkt mij dat als we de mogelijkheid verwachten dat een methode niet zal slagen, het falen ervan geen uitzondering is. Dus de methode die ik aanroep zou true moeten retourneren als het is gelukt, en false als het is mislukt.

Waarom is het een Func<bool, bool>en niet alleen een Func<bool>? Zodat als ik wileen methode om een ​​uitzondering te kunnen maken bij een mislukking, ik een manier heb om hem te informeren dat dit de laatste poging is.

Dus ik zou het kunnen gebruiken met code zoals:

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

of

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

Als het doorgeven van een parameter die de methode niet gebruikt lastig blijkt te zijn, is het triviaal om een ​​overbelasting van Retryte implementeren die slechts een Func<bool>als goed.


Antwoord 14

Update na 6 jaar:nu vind ik de onderstaande aanpak behoorlijk slecht. Om een ​​logica voor opnieuw proberen te creëren, moeten we overwegen een bibliotheek zoals Polly te gebruiken.


Mijn asyncimplementatie van de methode voor opnieuw proberen:

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

Belangrijkste punten: ik gebruikte .ConfigureAwait(false);en Func<dynamic>in plaats van Func<T>


Antwoord 15

Ik had een methode nodig die annulering ondersteunt, terwijl ik toch bezig was, heb ik ondersteuning toegevoegd voor het retourneren van tussentijdse fouten.

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

Je kunt de functie Retryop deze manier gebruiken, drie keer opnieuw proberen met een vertraging van 10 seconden, maar zonder te annuleren.

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

Of probeer het elke vijf seconden voor altijd opnieuw, tenzij geannuleerd.

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

Zoals je kunt raden, heb ik in mijn broncode de functie Retryoverbelast om de verschillende typen delegaties te ondersteunen die ik wil gebruiken.


Antwoord 16

Deze methode staat nieuwe pogingen toe op bepaalde uitzonderingstypes (gooit andere meteen weg).

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

Voorbeeld van gebruik:

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

Antwoord 17

Exponentiële uitstelis een goede strategie om opnieuw te proberen dan het x aantal keren proberen.&nbsp ;Je kunt een bibliotheek zoals Polly gebruiken om het te implementeren.


Antwoord 18

Voor degenen die zowel de mogelijkheid willen hebben om een ​​uitzondering opnieuw te proberen of expliciet het uitzonderingstype in te stellen, gebruik dit:

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

Antwoord 19

Hier is een async/await-versie die uitzonderingen verzamelt en annulering ondersteunt.

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

Antwoord 20

Ik heb twee implementaties van dit patroon met Polly. De ene is asynchroon.

Mijn synchrone methode is gebaseerd op dit antwoordvan Erik Bergstedt

public static T Retry<T>(Func<T> action, TimeSpan retryWait, int retryCount = 0)
{
    PolicyResult<T> policyResult = Policy
        .Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
        .WaitAndRetry(retryCount, retryAttempt => retryWait)
        .ExecuteAndCapture(action);
    if (policyResult.Outcome == OutcomeType.Failure)
    {
        throw policyResult.FinalException;
    }
    return policyResult.Result;
}

Async:

public static async Task<T> RetryAsync<T>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0)
{
    PolicyResult<T> policyResult = await Policy
        .Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
        .WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
        .ExecuteAndCaptureAsync(action);
    if (policyResult.Outcome == OutcomeType.Failure)
    {
        throw policyResult.FinalException;
    }
    return policyResult.Result;
}

Het zou ook gemakkelijk zijn om een ​​uitzonderingstype toe te staan, evenals de lambda voor het uitzonderingstype.


Antwoord 21

Of wat dacht je ervan om het wat netter te doen….

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

Ik ben van mening dat het gooien van uitzonderingen over het algemeen als mechanisme moet worden vermeden, tenzij je ze tussen grenzen doorgeeft (zoals het bouwen van een bibliotheek die andere mensen kunnen gebruiken). Waarom laat u de opdracht DoSomething()niet gewoon trueretourneren als het succesvol was en anders false?

EDIT:En dit kan ook worden ingekapseld in een functie zoals anderen hebben gesuggereerd. Het enige probleem is dat je de functie DoSomething()niet zelf schrijft


Antwoord 22

Ik moest een parameter doorgeven aan mijn methode om het opnieuw te proberen en een resultaatwaarde te krijgen; dus ik heb een uitdrukking nodig..
Ik bouw deze klas op die het werk doet (het is geïnspireerd op die van de LBushkin)
je kunt het als volgt gebruiken:

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.
de oplossing van LBushkin doet nog een poging =D


Antwoord 23

Ik zou de volgende code toevoegen aan het geaccepteerde antwoord

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

In feite maakt de bovenstaande code de klasse Retrygeneriek, zodat u het type uitzondering kunt doorgeven dat u wilt onderscheppen om het opnieuw te proberen.

Gebruik het nu bijna op dezelfde manier, maar specificeer het uitzonderingstype

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

Antwoord 24

Ik weet dat dit antwoord heel oud is, maar ik wilde hier gewoon commentaar op geven omdat ik problemen ben tegengekomen bij het gebruik van deze while, do, wat voor statement dan ook met tellers.

In de loop der jaren ben ik tot een betere aanpak gekomen, denk ik. Dat is om een ​​soort gebeurtenisaggregatie te gebruiken, zoals een reactieve extensie “Onderwerp” of iets dergelijks. Wanneer een try mislukt, publiceert u eenvoudig een gebeurtenis met de mededeling dat de poging is mislukt, en laat u de aggregatorfunctie de gebeurtenis opnieuw plannen. Dit geeft je veel meer controle over het opnieuw proberen zonder de aanroep zelf te vervuilen met een aantal lussen voor opnieuw proberen en wat niet. Je knoopt ook geen enkele draad vast met een heleboel draadslaapjes.


Antwoord 25

Doe het eenvoudig in C#, Java of andere talen:

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

en gebruik het heel eenvoudig in je code:

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

of je kunt het gebruiken in recursieve methoden:

void recursiveMethod(){
    //some code
    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }
    //some code    
    }

Antwoord 26

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

Antwoord 27

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

Antwoord 28

Ik heb een kleine les geschreven op basis van de antwoorden die hier zijn gepost. Hopelijk helpt het iemand: 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
}

Antwoord 29

Helper voor opnieuw proberen: een generieke Java-implementatie die zowel herbruikbare als ongeldige pogingen bevat.

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

Gebruik:

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

Antwoord 30

Ik heb zo een asynchrone versie van het geaccepteerde antwoord geïmplementeerd – en het lijkt goed te werken – opmerkingen?


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

En noem het gewoon zo:

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

Other episodes