tags:

views:

2037

answers:

8

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?

A: 
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
I see four bugs here :-)
Vinko Vrsalovic
+4  A: 
public void TryThreeTimes(Action action)
{
    int retries = 3;
    while(true) {
      try {
        action();
        break; // success!
      } catch {
        if(--retries == 0) throw;
        else Thread.Sleep(1000);
      }
   }
}

Then you would call:

TryThreeTimes(DoSomething);

...or alternatively...

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

You might like to extract the magic number '3' and the timeout period as arguments.

Drew Noakes
In your code `DoSomething` should be `action`
ChrisF
@ChrisF -- cheers
Drew Noakes
+32  A: 

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 RetryUtility
{
   public static void RetryAction( Action action, int numRetries, int retryTimeout )
   {
       if( action == null )
           throw new ArgumenNullException("action"); // slightly safer...

       do
       {
          try {  action(); return;  }
          catch
          { 
              if( numRetries <= 0 ) throw;  // improved to avoid silent failure
              else Thread.Sleep( retryTimeout );
           }
       } while( numRetries-- > 0 );
   }
}

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

RetryUtility.RetryAction( () => SomeFunctionThatCanFail(), 3, 1000 );
LBushkin
Change "if(numRetries == 0) throw;" to "if(numRetries >= 0) throw;" otherwise it silently fails when called with -1 for numRetries.
csharptest.net
Sorry to "if(numRetries <= 0) throw;". oops :)
csharptest.net
+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
@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
@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
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
Move the `if` out of the `do` loop, for God's sake!
Eduardo León
@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
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.
RichAmberale
is this possible in VB.Net? The 'Action' argument and Lambdas, I mean.
GaiusSensei
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
`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
Nicely done, I will use this!
Cocowalla
Additionally you don't want to ever retry if the exception has a "fatal" nature, like ExecutionEngineException, OutOfMemoryException, AccessViolationException, ThreadAbortException, etc.
Christian.K
+2  A: 

You might also consider adding the exception type you want to retry for. For instance is this a timeout exception you what 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.

csharptest.net
+1, but why not do RetryForException<T>(...) where T: Exception, then catch(T e)? Just tried it and it works perfectly.
TrueWill
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
@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
A: 

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

mrnye
"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
+1  A: 

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
+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
"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
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.
RichAmberale
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
+16  A: 

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.

Eric Lippert
+1. Raymond shares a real life example here, http://blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi
+1, well said. (15 characters)
csharptest.net
-1 This advice is useless for transient network failures encountered by automated batch processing systems.
nohat
+1  A: 

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 for return value
robbie