539

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();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

I would like to rewrite this in a general retry function like:

TryThreeTimes(DoSomething);

Is it possible in C#? What would be the code for the TryThreeTimes() method?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
noctonura
  • 12,763
  • 10
  • 52
  • 85
  • 1
    A simple cycle is not enough? Why just not to iterate over and execute logic for several times? – Restuta Oct 13 '09 at 22:08
  • 13
    Personally, I would be extremely wary of any such helper method. It's certainly possible to implement using lambdas, but the pattern itself is extremely smelly, so introducing a helper for it (which implies that it is frequently repeated) is in and of itself highly suspicious, and strongly hints at bad overall design. – Pavel Minaev Oct 13 '09 at 22:53
  • 14
    In my case, my DoSomething()s are doing stuff on remote machines such as deleting files, or trying to hit a network port. In both cases, there are major timing issues for when DoSomething will succeed and because of the remoteness, there is no event I can listen on. So yeah, its smelly. Suggestions welcome. – noctonura Oct 13 '09 at 22:58
  • 26
    @PavelMinaev why would using retries hint at bad overall design? If you write a lot of code that connects integration points then using retries is definitely a pattern you should seriously consider using. – bytedev Aug 23 '16 at 16:32

30 Answers30

643

Blanket catch statements that simply retry the same call can be dangerous if used as a general exception handling mechanism. Having said that, here's a lambda-based retry wrapper that you can use with any method. I chose to factor the number of retries and the retry timeout out as parameters for a bit more flexibility:

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

You can now use this utility method to perform retry logic:

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

or:

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

or:

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

Or you could even make an async overload.

VCD
  • 889
  • 10
  • 27
LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • 7
    +1, especially for the warning and error-checking. I'd be more comfortable if this passed in the type of the exception to catch as a generic parameter (where T: Exception), though. – TrueWill Oct 13 '09 at 22:55
  • @TrueWill, agreed, I solved it with the ugly Type argument in RetryForExcpetionType below; however the principal of looking for a specific exception rather than any exception should be applied. – csharptest.net Oct 13 '09 at 23:18
  • @LBushkin, the method action() executes 4 times when you specify numRetries == 3. Was that the intent? If so you should call attention to the difference in behavior to the OP implementation. I think your way makes more sense when you stop and think about it. – csharptest.net Oct 13 '09 at 23:28
  • 2
    It was my intent that "retries" actually meant retries. But it's not too hard to change it to mean "tries". As long as the name is kept meaningful. There are other opportunities to improve the code, like checking for negative retries, or negative timeouts - for example. I omitted these mostly to keep the example simple ... but again, in practice these would probably be good enhancements to the implementation. – LBushkin Oct 14 '09 at 00:05
  • Move the `if` out of the `do` loop, for God's sake! – isekaijin Oct 14 '09 at 00:09
  • 1
    @Eduardo: Moving the if out of the do loop would result in the catch statement sleeping even when the retry count is zero. Not something that I think is generally desirable. Do you have a way to avoid that without the conditional check inside the loop? – LBushkin Oct 14 '09 at 00:21
  • Ya I noticed the retry would happen > once on success but the answer to use Action (which I was not familiar with) is the main thing I was looking for. – noctonura Oct 14 '09 at 04:05
  • As mentioned in RichAmberale's comment, there's a bug! This code will execute `action` multiple times even when there are no exceptions. Surely `action` should only be called again if the previous call(s) failed. – LukeH Oct 14 '09 at 12:21
  • `Action` is a .NET framework type that is accessible from VB. VB2008 also has expression lambdas, but not statement lambdas (though the latter will be in VB2010). – Pavel Minaev Oct 14 '09 at 21:07
  • Additionally you don't want to ever retry if the exception has a "fatal" nature, like ExecutionEngineException, OutOfMemoryException, AccessViolationException, ThreadAbortException, etc. – Christian.K Jan 21 '10 at 07:19
  • 58
    We use a similar pattern for our DB access in a high volume Biztalk App, but with two improvements: We have blacklists for exceptions that shouldn't be retried and we store the first exception that occurs and throw that when the retry ultimately fails. Reason being that the second and following exceptions often are different from the first one. In that case you hide the initial problem when rethrowing only the last exception. – TToni Mar 22 '12 at 11:21
  • @TToni When you say throw your first exception, will it clear the stack trace? – Dexters Mar 29 '13 at 13:00
  • 3
    @Dexters We throw a new exception with the original exception as inner exception. The original stack trace is available as attribute from the inner exceptions. – TToni Mar 31 '13 at 21:31
  • I think you can use `while(true)` instead of `while( numRetries-- > 0 )` and decrease the retries in the if-statement `if( numRetries-- <= 0 ) throw;` – Jaider Apr 19 '13 at 15:12
  • 1
    3 bugs are here. At least for .net 4.0. – Alex Blokha Sep 28 '13 at 19:53
  • 1
    Suggestion: Pass in an exception as a Type (defaulted to null), and only perform the retry if a specific exception matches the passed in type. – Michael Brown May 14 '14 at 20:30
  • Also note that in case of client timeout, it could be possible that on the server side the transaction WAS completed, but just not in the timeout interval the client side had defined. This would mean the same action would be executed multiple times. E.g.: the action provided as parameter calls an external service. That service takes 2 minutes to complete. However, the action has specified a timeout of 1 minute. This will result in the action being executed multiple times on the server side, even though your code will throw the AggregateException. – Nullius Jul 15 '15 at 13:23
  • I would change the inner retry loop to: `try { if (retry > 0) Thread.Sleep(retryInterval);` ... So you don't actually delay after the last attempt was executed. – Loudenvier Aug 14 '15 at 12:50
  • 10
    You could also try using an open source library such as [Polly](https://www.nuget.org/packages/Polly/) to handle this. There is much more flexibility for waiting between retries and it has been validated by many others that have used the project. Example: ```Policy.Handle().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });``` – Todd Meinershagen Aug 25 '15 at 02:28
  • I very much like this approach for simplicity and amount of control. I picked it up in this very response and would like to share an addition I developed. Instead of `maxAttemptCount`, I created an `IRetryHandler` to be used in this method with fallback to a simple default implementation. It should take the exceptions as its argument and must count the tries and whatever infrastructure required for proper handling. This allows you fine control of the handling and retry strategy while keeping it separate from the actual retry logic which is simple. – Nomenator Sep 28 '18 at 19:01
  • does Thread.Sleep block that thread or does it yield and wakes thread up to check timer? The latter is fine. However, the first can consume many threads all at once – Saher Ahwal Oct 03 '20 at 00:12
  • 1
    Why is this so convoluted? You create a generic method just to make it return `null`, then you have to type `T` as `object` to make it compile. Did you copy it from somewhere? – Jakub Fojtik Nov 11 '20 at 21:45
  • 1
    @SaherAhwal It will block the thread. It probably is better to use await `Task.Delay(retryInterval);` as said in https://stackoverflow.com/questions/8815895/why-is-thread-sleep-so-harmful/8815944#8815944 – Mykhailo Bykhovtsev Jan 28 '22 at 21:37
276

You should try Polly. It's a .NET library written by me that allows developers to express transient exception handling policies such as Retry, Retry Forever, Wait and Retry or Circuit Breaker in a fluent manner.

Example

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(3))
    .Execute(DoSomething);
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Michael Wolfenden
  • 2,911
  • 1
  • 13
  • 7
  • 3
    What is actually the OnRetry delegate? I assume it is what we need to perform when exception is occurred. So when exception is occurred OnRetry delegate will call and afterwards Execute delegate. Is it so? – user6395764 May 09 '18 at 09:44
  • Where I should use this snippet code? if the answer is startup.cs, how to register Policy? – Sina Riani Jun 01 '21 at 09:43
  • Q: What is actually the OnRetry delegate? A: It only allows you to do something when a retry is performed (e.g., log something). You don't need to call Execute in there, that happens automatically. – D.R. Jan 04 '22 at 15:08
  • @SinaRiani You can do something like this with Polly. https://stackoverflow.com/a/68013076/4267686 – Keith Banner May 11 '22 at 21:02
76
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

Then you would call:

TryThreeTimes(DoSomething);

...or alternatively...

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

A more flexible 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);
        }
   }
}

To be used as:

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

A more modern version with support for 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);
        }
   }
}

To be used as:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 2
    preferably change the if to: `--retryCount <= 0` because this will go on forever if you want to disable retries by setting it to 0. Technically the term `retryCount` isn't a really good name, because it won't retry if you set it to 1. either rename it to `tryCount` or put the -- behind. – Stefanvds May 31 '16 at 06:47
  • 2
    @saille I agree. However the OP (and all other answers) are using `Thread.Sleep`. Alternatives are to use timers, or more likely nowadays to use `async` to retry, with `Task.Delay`. – Drew Noakes Oct 04 '17 at 10:22
  • 3
    I've added an async version. – Drew Noakes Oct 04 '17 at 10:26
  • Only ***break*** if action `returns true` ? `Func` – Kiquenet Apr 18 '18 at 14:08
  • @DrewNoakes Is there any benefits to use `async` version than the the other versions? – ibda Feb 02 '22 at 10:10
  • 1
    @ibda you'd only use the async version if you want to free the thread up to do other work, rather than sleeping. If you run work on the thread pool, for example, you shouldn't be putting those threads to sleep. – Drew Noakes Feb 02 '22 at 20:41
57

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 a notification about a failure three times on packet failure.

Now suppose the notification layer reactivates the notification three times on a 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.

Pang
  • 9,564
  • 146
  • 81
  • 122
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 19
    +1. Raymond shares a real life example here, http://blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx – SolutionYogi Oct 14 '09 at 13:57
  • 245
    -1 This advice is useless for transient network failures encountered by automated batch processing systems. – nohat Sep 24 '10 at 21:22
  • 17
    Not sure if this is saying "Don't do it" followed by "do it". Most of the people asking this question are probably the people working in the software abstraction. – Jim L Apr 12 '12 at 16:30
  • 56
    When you have long running batch jobs that use network resources, such as web services, you can't expect the network to be 100% reliable. There are going to be occasional timeouts, socket disconnects, possibly even spurious routing glitches or server outages that occur while you are using it. One option is to fail, but that may mean restarting a lengthy job later. Another option is to retry a few times with suitable delay to see if it's a temporary problem, then fail. I agree about composition, which you have to be aware of.. but it's sometimes the best choice. – Erik Funkenbusch Feb 04 '13 at 22:13
  • 1
    As @Mystere Man says, there is situations where it doesn't necessarily serve the user to get hit by a retry dialog instantly. But if you must, the scenario here is grim, so don't ever use "Retry X number of times"... if you must retry automatically, then use "Retry every X for Y time", e.g. "Retry every 100 ms for 500 ms"... Then you can ensure that it won't grow in time for each layer, because the top layers would never retry as the maximum time would be exceeded by the lower layer retries. – Jens Apr 22 '13 at 11:14
  • 24
    I think that the quote you used at the beginning of your answer is interesting. "Expecting different results" is only insanity if prior experience regularly gives you the same results. While software is built on a promise of consistency, there are definitely circumstances where we are required to interact with unreliable forces outside of our control. – Michael Richardson May 21 '14 at 13:50
  • 3
    @probackpacker: Sure -- the question then is "over what time scale is the unpredictable force likely to change?" A power spike that makes a network router unresponsive for a millisecond, and someone powering off the router for an hour, are very different things! (Another way to criticize my quote is that of course that is *not* anything even *vaguely* like the actual definition of insanity.) – Eric Lippert May 21 '14 at 14:41
  • 3
    @probackpacker: Case in point: a situation where I *do* use the "try, try again" approach is when I am writing a file, closing it, and then opening it again soon after. (Think of a log file used to diagnose a program that is crashing unpredictably; I don't know when the last close is going to be and I don't want to lose any data.) It is *very* common for badly-written virus checkers to lock the file the *moment* it is closed and then spend a few milliseconds checking it for viruses; if I attempt to open the file in those few milliseconds it will fail, but likely succeed on the second try. – Eric Lippert May 21 '14 at 14:45
  • @EricLippert: I couldn't agree more, and I still think your answer is valuable (+1 btw). Thanks for your clarification of a situation where you _do_ use retry logic. I'm currently working with some C# code that uses async/await to repeatedly write to the database. Sometimes everything goes well, sometimes it fails miserably, and it's unclear as to why. In light of that, I read your answer and couldn't help but reflect on the assumption behind the quote. – Michael Richardson May 21 '14 at 14:55
  • 5
    Actually I think it is the definition of non-deterministic. – CZahrobsky Oct 22 '15 at 18:59
  • When you have an automated process that is connecting to databases or reading data from the web in general, things may go wrong, but can be solved just by adding a retry logic. I would say it is mandatory to implement retry logic for these cases. – JackCid May 15 '18 at 16:13
  • 1
    @JuanAndrésCidMolina: When you have an automated process that is connecting to network resources, things may go wrong, and can be made orders of magnitude worse just by adding retry logic. I would say it is mandatory to NOT implement retry logic in those cases. I am more interested in avoiding harm to the user than avoiding inconvenience. – Eric Lippert May 15 '18 at 17:02
  • @EricLippert true that. It actually depends on what you are doing. – JackCid Jun 03 '20 at 00:45
  • 2
    Poor advice. For example, the exponential backoff pattern is well-known and commonly used in large distributed systems. Here is one of many sources referencing this pattern: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-retries-exponential-backoff and the correct answer should guide the OP towards this pattern, instead of simply saying "don't do it". – BartoszKP Jun 07 '20 at 21:28
  • 1
    @BartoszKP: I am somewhat confused by your comment. How does exponentially backing off retries prevent long delays caused by composition of this pattern with itself? It seems to me that it would make that worse, not better. Exponential backoff mitigates unwanted DOSsing of one's own service; to my understanding it is not a mitigation of the issue I raised in this answer, which is about composition. Can you explain? – Eric Lippert Jun 10 '20 at 21:01
  • @EricLippert Yes, we are talking about different things. However, your answer as it is, to the question as it is reads to me "don't do it". This is poor advice since there are valid reasons to do it, and commonly used patterns to do it (hence my example). Thus suggesting it is a bad idea overall is misleading IMHO, especially if what follows is a lot of "suppose" for a very specific example - assuming OP is asking about this type of errors (where most probably they are not). And also, the maxim you quote is irrelevant in systems where transient errors do happen. – BartoszKP Jun 15 '20 at 17:09
  • @BartoszKP: I take your point. However. pointing out "that maxim does not apply in all the cases where it does not apply" is not an actionable criticism. "X doesn't apply in the cases where it does not apply" is a tautology. – Eric Lippert Jun 15 '20 at 21:02
  • 1
    @EricLippert You start with this maxim as a standalone argument (you say "First", the whole specific example is attached to the part you start with "Second"). I'm sure you didn't mean it, but this first part feels like talking down to someone who was silly enough to even think about doing something twice and expecting different results :-) Sorry if this feels like nitpicking, I honestly think that while the second part of your answer is a valuable example, the first part starts off with the reader on the wrong foot. – BartoszKP Jun 17 '20 at 09:23
  • 2
    @BartoszKP: You've started with an irrelevant technical critique which you admit does not have anything to do with the issue I've raised in the answer, and now you've moved on to a critique of tone. I'll only be responding further to substantive critiques on technical merits, rather than on word choice and your interpretation of my "tone". I encourage you to limit your critiques to substantive ones in general. – Eric Lippert Jun 17 '20 at 21:56
  • 1
    @EricLippert The technical critique is relevant - you present a specific scenario and claim (see your first point) that it disproves the idea in general, which is _technically_ incorrect. I encourage you not to skip the technical part of my comments instead of focusing on the non-technical suggestion. I think I have nothing more to add, so I'll stop here - have a good day/night and thank you for your time. – BartoszKP Jun 18 '20 at 19:23
  • https://devblogs.microsoft.com/oldnewthing/archive/2005/11/07/489807.aspx not found – Kiquenet Feb 02 '21 at 15:07
  • @Kiquenet: https://devblogs.microsoft.com/oldnewthing/20051107-20/?p=33433 – Eric Lippert Feb 02 '21 at 15:22
34

The Transient Fault Handling Application Block provides an extensible collection of retry strategies including:

  • Incremental
  • Fixed interval
  • Exponential back-off

It also includes a collection of error detection strategies for cloud-based services.

For more information see this chapter of the Developer's Guide.

Available via NuGet (search for 'topaz').

Matthew Lock
  • 13,144
  • 12
  • 92
  • 130
Grigori Melnik
  • 4,067
  • 2
  • 34
  • 40
  • 1
    Interesting. Can you use this outside of Windows Azure, say in a Winforms app? – Matthew Lock Jan 22 '13 at 05:18
  • 7
    Absolutely. Use the core retry mechanism and provide your own detection strategies. We intentionally decoupled those. Find the core nuget package here: http://nuget.org/packages/TransientFaultHandling.Core – Grigori Melnik Jan 22 '13 at 15:13
  • Updated doc can be found here: http://msdn.microsoft.com/en-us/library/dn440719(v=pandp.60).aspx – Grigori Melnik Dec 03 '13 at 00:49
  • 2
    Also, the project is now under Apache 2.0 and accepting community contributions. http://aka.ms/entlibopen – Grigori Melnik Dec 03 '13 at 00:50
  • 1
    @Alex. The pieces of it are making it into the platform. – Grigori Melnik Apr 14 '15 at 16:15
  • 3
    This is now deprecated, and last I used it it contained some bugs that as far as I know weren't, and never will be fixed: https://github.com/MicrosoftArchive/transient-fault-handling-application-block. – Ohad Schneider Aug 22 '17 at 14:12
18

I'm a fan of recursion and extension methods, so here are my two cents:

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

        InvokeWithRetries(@this, --numberOfRetries);
    }
}
Martin R-L
  • 4,039
  • 3
  • 28
  • 28
15

Allowing for functions and retry messages

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;
}
Brian
  • 1,845
  • 1
  • 22
  • 37
14

You might also consider adding the exception type you want to retry for. For instance is this a timeout exception you want to retry? A database exception?

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

You might also note that all of the other examples have a similar issue with testing for retries == 0 and either retry infinity or fail to raise exceptions when given a negative value. Also Sleep(-1000) will fail in the catch blocks above. Depends on how 'silly' you expect people to be but defensive programming never hurts.

Zain Rizvi
  • 23,586
  • 22
  • 91
  • 133
csharptest.net
  • 62,602
  • 11
  • 71
  • 89
  • 10
    +1, but why not do RetryForException(...) where T: Exception, then catch(T e)? Just tried it and it works perfectly. – TrueWill Oct 14 '09 at 01:23
  • Either or here since I don't need to do anything with the Type provided I figured a plain old parameter would do the trick. – csharptest.net Oct 14 '09 at 01:49
  • @TrueWill apparently catch(T ex) has some bugs according to this post http://stackoverflow.com/questions/1577760/why-cant-i-catch-a-generic-exception-in-c – csharptest.net Oct 16 '09 at 14:15
  • 3
    Update: Actually a better implementation I've been using takes a Predicate delegate that returns true if a retry is appropriate. This allows you to use native error codes or other properties of the exception to determine if a retry is applicable. For instance HTTP 503 codes. – csharptest.net Apr 16 '12 at 18:44
  • @csharptest.net: the SO link you posted actually concludes that the bug is only evident under the VS debugger (with .NET 3.5). I've tested catch (T ex) and it works perfectly in VS 2010 both under the debugger and otherwise – Sudhanshu Mishra Feb 20 '13 at 06:29
  • 1
    "Also Sleep(-1000) will fail in the catch blocks above" ... use a TimeSpan and you won't get this problem. Plus TimeSpan is much more flexible and self descriptive. From your signature of "int retryTimeout" how do i know if retryTimeout is MS, seconds, minutes, years?? ;-) – bytedev Aug 24 '16 at 10:23
9

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);
Erik Bergstedt
  • 912
  • 10
  • 27
9

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);
    }
}
Jogge
  • 1,654
  • 12
  • 36
Anders Skovborg
  • 234
  • 3
  • 5
  • 4
    I am kind of curious, would this spawn an insane amount of threads with a high retry count and interval because of returning the same awaitable method? – HuntK24 Jun 15 '17 at 22:37
9

Implemented LBushkin's answer in the latest fashion:

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

and to use it:

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

whereas the function [TaskFunction] can either be Task<T> or just Task.

Fabian Bigler
  • 10,403
  • 6
  • 47
  • 70
7

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 ) );
Igor Pashchuk
  • 2,455
  • 2
  • 22
  • 29
  • 1
    This is excellent. But personally I wouldn't call it 'retryTimeout' as it's not really a Timeout. 'RetryDelay', perhaps? – Holf Jun 10 '15 at 11:17
5

I have two implementations of this pattern using Polly. One is async.

My synchronous method is based on this answer by 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;
}

It would also be easy to allow an exception type to be passed in as well as the lambda for the exception type.

Keith Banner
  • 602
  • 1
  • 10
  • 15
4

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.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • 1
    +1 for avoiding the exception. Though I'd do a void Retry(...) and throw something? Boolean returns and/or return codes are too often overlooked. – csharptest.net Oct 13 '09 at 23:22
  • 2
    "if we're expecting the possibility that a method won't succeed, its failure isn't an exception" - while that's true in some cases, exception need not imply exceptional. It's for error handling. There is no guarantee that the caller will check a Boolean result. There **is** a guarantee that an exception will be handled (by the runtime shutting down the application if nothing else does). – TrueWill Oct 14 '09 at 01:17
  • I can't find the reference but I believe .NET defines an Exception as "a method didn't do what it said it will do". 1 purpose is to use exceptions to indicate a problem rather than the Win32 pattern of requiring the caller to check the return value if the function succeeded or not. – noctonura Oct 14 '09 at 04:03
  • But exceptions don't merely "indicate a problem." They also include a mass of diagnostic information that costs time and memory to compile. There are clearly situations in which that doesn't matter the least little bit. But there are a lot where it does. .NET doesn't use exceptions for control flow (compare, say, with Python's use of the `StopIteration` exception), and there's a reason. – Robert Rossney Oct 14 '09 at 08:56
  • The `TryDo` method pattern is a slippery slope. Before you know it, your entire call stack will consist of `TryDo` methods. Exceptions were invented to avoid such a mess. – HappyNomad Feb 01 '20 at 16:59
4

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>

Cihan Uygun
  • 2,128
  • 1
  • 16
  • 26
  • 1
    This does not provide an answer to the question. Please consider posting your answer as a new question, using the "Ask Question" button at the top of the page, then posting your own answer to the question to share what you learned with the community. – elixenide Mar 07 '14 at 05:47
  • Much simpler with C# 5.0 than codereview.stackexchange.com/q/55983/54000 but maybe CansellactionToken should be injected. – SerG Dec 11 '14 at 10:32
  • 2
    There's a problem with this implementation. After the final retry, right before giving up, `Task.Delay` is called for no reason. – HappyNomad Feb 01 '20 at 16:04
  • 1
    @HappyNomad this is a 6 years old answer and now I consider that its a pretty bad approach to create a retry logic :)) thanks for notification. I will update my answer according to that consideration. – Cihan Uygun Feb 02 '20 at 08:59
3

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.

Patrick
  • 1,717
  • 7
  • 21
  • 28
Jodrell
  • 34,946
  • 5
  • 87
  • 124
3

This method allows retries on certain exception types (throws others immediately).

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

Example usage:

DoRetry(typeof(IOException), () => {
    using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(entryBytes, 0, entryBytes.Length);
    }
});
Tom Gullen
  • 61,249
  • 84
  • 283
  • 456
2

Exponential backoff is a good retry strategy than simply trying x number of times. You can use a library like Polly to implement it.

utsavized
  • 126
  • 2
2

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);
    }
}
mrogunlana
  • 827
  • 9
  • 11
1

Here's an async/await version that aggregates exceptions and supports cancellation.

/// <seealso href="https://learn.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 );
}
HappyNomad
  • 4,458
  • 4
  • 36
  • 55
0

I had the need to pass some parameter to my method to retry, and have a result value; so i need an expression.. I build up this class that does the work (it is inspired to the the LBushkin's one) you can use it like this:

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. the LBushkin's solution does one more retry =D

Paolo Sanchi
  • 783
  • 9
  • 19
0

I would add the following code to the accepted answer

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

Basically the above code is making the Retry class generic so you can pass the type of the exception you want to catch for retry.

Now use it almost in the same way but specifying the exception type

Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
Juan M. Elosegui
  • 6,471
  • 5
  • 35
  • 48
  • The for loop will always execute a couple of times (based on your retryCount) even if the code in TRY CATCH loop was executed without exceptions. I would suggest to set retryCount equal to the retry var in the try loop, so the for loop wil stop going over it. – scre_www Sep 08 '17 at 08:32
  • @scre_www I believe you're mistaken. If `action` doesn't throw then `Do` returns thus `break`ing away from the `for` loop. – HappyNomad Feb 01 '20 at 15:46
  • In any case, there's a problem with this implementation. After the final retry, right before giving up, `Thread.Sleep` is called for no reason. – HappyNomad Feb 01 '20 at 15:48
0

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.

Brandon
  • 830
  • 1
  • 15
  • 35
0

Do it simple in C#, Java or other languages:

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

and use it in your code very simple:

 void simpleMethod(){
    //some code

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

    //some code    
    }

or you can use it in recursive methods:

void recursiveMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }

    //some code    
    }
Choletski
  • 7,074
  • 6
  • 43
  • 64
-1

Or how about doing it a bit neater....

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

I believe throwing exceptions should generally be avoided as a mechanism unless your a passing them between boundaries (such as building a library other people can use). Why not just have the DoSomething() command return true if it was successful and false otherwise?

EDIT: And this can be encapsulated inside a function like others have suggested as well. Only problem is if you are not writing the DoSomething() function yourself

mike
  • 3,146
  • 5
  • 32
  • 46
  • 8
    "I believe throwing exceptions should generally be avoided as a mechanism unless your a passing them between boundaries" - I completely disagree. How do you know the caller checked your false (or worse, null) return? WHY did the code fail? False tells you nothing else. What if the caller has to pass the failure up the stack? Read http://msdn.microsoft.com/en-us/library/ms229014.aspx - these are for libraries, but they make just as much sense for internal code. And on a team, other people are likely to call your code. – TrueWill Oct 14 '09 at 01:28
-1
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);
    }
Danh
  • 5,916
  • 7
  • 30
  • 45
Bhaskar
  • 49
  • 2
-2
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);          
      }
}
Mark P Neyer
  • 1,009
  • 2
  • 8
  • 19
  • 1
    Because the `throw;` is the only way the infinite loop is terminated, this method is actually implementing "try until it fails N times" and not the desired "try up to `N` times until it succeeds". You need a `break;` or `return;` after the call to `ThingToTryDelegate();` otherwise it'll be called continuously if it never fails. Also, this won't compile because the first parameter of `TryNTimes` has no name. -1. – Lance U. Matthews Sep 07 '17 at 07:16
-2

I've written a small class based on answers posted here. Hopefully it will help someone: 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

}
natenho
  • 5,231
  • 4
  • 27
  • 52
-2

Retry helper: a generic java implementation that contains both returnable and void type retries.

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

Usage:

    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();
    }
divya jain
  • 59
  • 7
-2

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);
  • `Thread.Sleep`? Blocking a thread negates the benefits of asynchrony. Also I am pretty sure that the `Task DoAsync()` version should accept an argument of type `Func`. – Theodor Zoulias May 13 '20 at 15:50