Cleanest way to write retry logic?

前端 未结 29 2523
旧巷少年郎
旧巷少年郎 2020-11-22 03:01

Occasionally I have a need to retry an operation several times before giving up. My code is like:

int retries = 3;
while(true) {
  try {
    DoSomething();
         


        
相关标签:
29条回答
  • 2020-11-22 03:35

    Building on the previous work, I thought about enhancing the retry logic in three ways:

    1. Specifying what exception type to catch/retry. This is the primary enhacement as retrying for any exception is just plain wrong.
    2. Not nesting the last try in a try/catch, achieving slightly better performance
    3. Making it an Action extension method

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

    The method can then be invoked like so (anonymous methods can be used as well, of course):

    new Action( AMethodThatMightThrowIntermittentException )
      .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
    
    0 讨论(0)
  • 2020-11-22 03:35

    I needed a method that supports cancellation, while I was at it, I added support for returning intermediate failures.

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

    You can use the Retry function like this, retry 3 times with a 10 second delay but without cancellation.

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

    Or, retry eternally every five seconds, unless cancelled.

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

    As you can guess, In my source code I've overloaded the Retry function to support the differing delgate types I desire to use.

    0 讨论(0)
  • 2020-11-22 03:35
    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);          
          }
    }
    
    0 讨论(0)
  • 2020-11-22 03:36

    Update after 6 years: now I consider that the approach below is pretty bad. To create a retry logic we should consider to use a library like Polly.


    My async implementation of the retry method:

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

    Key points: I used .ConfigureAwait(false); and Func<dynamic> instead Func<T>

    0 讨论(0)
  • 2020-11-22 03:39
    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);
        }
    
    0 讨论(0)
  • 2020-11-22 03:40

    Keep it simple with 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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题