Cleanest way to write retry logic?

前端 未结 29 2570
旧巷少年郎
旧巷少年郎 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:29

    For those who want to have both the option to retry on any exception or explicitly set the exception type, use this:

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

    I've implemented an async version of the accepted answer like so - and it seems to work nicely - any comments?

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

    And, call it simply like this:

    var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);
    
    0 讨论(0)
  • 2020-11-22 03:31

    Use Polly

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

    Here is a retry-generic I use with 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;
    }
    

    Use it like this

    var result = Retry(() => MyFunction()), 3);
    
    0 讨论(0)
  • 2020-11-22 03:34

    This is possibly a bad idea. First, it is emblematic of the maxim "the definition of insanity is doing the same thing twice and expecting different results each time". Second, this coding pattern does not compose well with itself. For example:

    Suppose your network hardware layer resends a packet three times on failure, waiting, say, a second between failures.

    Now suppose the software layer resends an notification about a failure three times on packet failure.

    Now suppose the notification layer reactivates the notification three times on an notification delivery failure.

    Now suppose the error reporting layer reactivates the notification layer three times on a notification failure.

    And now suppose the web server reactivates the error reporting three times on error failure.

    And now suppose the web client resends the request three times upon getting an error from the server.

    Now suppose the line on the network switch that is supposed to route the notification to the administrator is unplugged. When does the user of the web client finally get their error message? I make it at about twelve minutes later.

    Lest you think this is just a silly example: we have seen this bug in customer code, though far, far worse than I've described here. In the particular customer code, the gap between the error condition happening and it finally being reported to the user was several weeks because so many layers were automatically retrying with waits. Just imagine what would happen if there were ten retries instead of three.

    Usually the right thing to do with an error condition is report it immediately and let the user decide what to do. If the user wants to create a policy of automatic retries, let them create that policy at the appropriate level in the software abstraction.

    0 讨论(0)
  • 2020-11-22 03:34

    I'd implement this:

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

    I wouldn't use exceptions the way they're used in the other examples. It seems to me that if we're expecting the possibility that a method won't succeed, its failure isn't an exception. So the method I'm calling should return true if it succeeded, and false if it failed.

    Why is it a Func<bool, bool> and not just a Func<bool>? So that if I want a method to be able to throw an exception on failure, I have a way of informing it that this is the last try.

    So I might use it with code like:

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

    or

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

    If passing a parameter that the method doesn't use proves to be awkward, it's trivial to implement an overload of Retry that just takes a Func<bool> as well.

    0 讨论(0)
  • 2020-11-22 03:34

    I know this answer is very old but I just wanted to comment on this because I have run into issues using these while, do, whatever statement with counters.

    Over the years I have settled on a better approach I think. That is to use some sort of event aggregation like a reactive extensions "Subject" or the like. When a try fails, you simply publish an event saying the try failed, and have the aggregator function re-schedule the event. This allows you much more control over the retry without polluting the call itself with a bunch of retry loops and what not. Nor are you tying up a single thread with a bunch of thread sleeps.

    0 讨论(0)
提交回复
热议问题