Cleanest way to write retry logic?

前端 未结 29 3147
旧巷少年郎
旧巷少年郎 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条回答
  •  Happy的楠姐
    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();
            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
        {
            private readonly IEnumerable failureExceptions;
            private readonly int failureCount;
    
             protected internal RetryResult(
                 ICollection failureExceptions)
             {
                 this.failureExceptions = failureExceptions;
                 this.failureCount = failureExceptions.Count;
             }
        }
    
        public int FailureCount
        {
            get { return this.failureCount; }
        }
    
        public IEnumerator 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.

提交回复
热议问题