7

I am developing a WPF 4.0 application in which we get the data from a remote web service. The web service exposes around 120+ methods to its clients. If a web service call from my WPF application fails, I need to retry it n times which is configurable via App.Config. How to implement this? Are there any design patterns that address this problem?

funwithcoding
  • 2,412
  • 2
  • 30
  • 41
  • 4
    Note that this pattern does not compose well with itself. If a function that retries four times calls a function which retries four times, and that calls a function which retries four times, then the last operation is retried 64 times. If it waits 30 seconds between retries then the user sits there for half an hour waiting for the error message. I strongly recommend against this pattern. When something fails, stop *immediately*, tell the user, and let them decide whether to retry or whether to go look and see if the router is unplugged. – Eric Lippert Jan 31 '11 at 16:20
  • Ofcourse for WPF Apps, the retry number wont be high! Nevertheless this is highly useful for console applications which perform background operations. – funwithcoding Jan 31 '11 at 16:47
  • What did you end up doing? Don't forget to mark an answer? – Dustin Davis Feb 02 '11 at 21:38

7 Answers7

9
static T TryNTimes<T>(Func<T> func, int times)
{
  while (times>0)
  {
     try
     {
        return func();
     }
     catch(Exception e)
     {
       if (--times <= 0)
          throw;
     }

  }
}
Itay Karo
  • 17,924
  • 4
  • 40
  • 58
4

I wrote this code not too long ago to do something similar to what you want. It can be modified to fit your needs. It's a generic wait method. Pass in a function and if the expected result is not returned, wait then retry and exit after X number of tries.

/// <summary>
    /// Wait for the result of func to return the expeceted result
    /// </summary>
    /// <param name="func">Function to execute each cycle</param>
    /// <param name="result">Desired result returned by func</param>
    /// <param name="waitInterval">How long to wait (ms) per cycle </param>
    /// <param name="cycles">How many times to execute func before failing</param>
    /// <returns>True if desired result was attained. False if specified time runs out before desired result is returned by func</returns>
    protected static bool WaitForEvent(Func<bool> func, bool result, int waitInterval, int cycles)
    {
        int waitCount = 0;
        while (func() != result)
        {
            if (waitCount++ < cycles)
            {
                Thread.Sleep(waitInterval);
            }
            else
            {
                return false;
            }
        }

        return true;

    }
Dustin Davis
  • 14,482
  • 13
  • 63
  • 119
  • Could be a good idea to increase the wait interval with each attempt. – CodesInChaos Jan 31 '11 at 16:08
  • thats what if (waitCount++ < cycles) does – Dustin Davis Jan 31 '11 at 16:09
  • 1
    That's not solving the OP problem, which is to retry on exceptions. – Mark Rendle Jan 31 '11 at 16:16
  • Your code has constant wait time between each attempt. Often it's a good idea to retry quickly at first and then increasingly slower. – CodesInChaos Jan 31 '11 at 16:16
  • Actually it does. If no modifications are made to this method, the Func<>() passed in can wrap what ever logic he needs and can return true/false on exception which will cause a wait/retry. Otherwise this code can be modified to include a try/catch. – Dustin Davis Jan 31 '11 at 16:18
  • 1
    Actually it doesn't, since you neglected to include the example of "wrapping whatever logic is needed and returning true/false..." in your rush to copy and paste some half-relevant code. – Mark Rendle Jan 31 '11 at 17:52
1
while(retries < maxTries)
   try
   {
      //retryable code here
      break;
   }
   catch(Exception ex)
   {
      if(++retries == maxTries)
         throw;
      continue;
   }

Certainly nothing fancy but it'll get the job done. The main pattern, which would be common to pretty much any implementation, is some looping construct containing and somewhat controlled by a try-catch; that can either be a recursive call or some iterative loop such as the while loop above. Make sure you exit the loop properly after a successful attempt, and keep track of retries; failure to do either will cause an infinite loop.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • And If he puts this in a function then he has a central point for the 120+ method calls. – Uwe Keim Jan 31 '11 at 16:06
  • Doesn't appear to have any wait interval defined – NotMe Jan 31 '11 at 16:07
  • 1
    Waiting isn't always necessary; it depends on what exactly you're trying to do in the try block. If you need to make sure a remote computer has finished cleaning up after an unsuccessful attempt, by all means wait. However, Thread.Sleep() has to be used carefully or it'll cause the app to stop responding; that widens the scope of what you have to do to implement something like this cleanly. – KeithS Jan 31 '11 at 16:13
  • I certainly like this better than my GOTO suggestion. +1 – Matt Jan 31 '11 at 16:13
1

You can use a functional approach to this:

class Program
{
    static T Retry<T, TException>(Func<T> thingToTry, int timesToRetry)
        where TException : Exception
    {
        // Start at 1 instead of 0 to allow for final attempt
        for (int i = 1; i < timesToRetry; i++)
        {
            try
            {
                return thingToTry();
            }
            catch (TException)
            {
                // Maybe: Trace.WriteLine("Failed attempt...");
            }
        }

        return thingToTry(); // Final attempt, let exception bubble up
    }

    static int ServiceCall()
    {
        if (DateTime.Now.Ticks % 2 == 0)
        {
            throw new InvalidOperationException("Randomly not working");
        }

        return DateTime.Now.Second;
    }

    static void Main()
    {
        int s = Retry<int, InvalidOperationException>(ServiceCall, 10);
    }
}

You can use this to catch specific exceptions (add more TException generic parameters if necessary).

Mark Rendle
  • 9,274
  • 1
  • 32
  • 58
  • 1
    Why use recursive? Performance and memory issues are going to arise there not to mention (as you pointed out) a stack overflow possibility. Good try, but bad design IMO. – Dustin Davis Jan 31 '11 at 16:20
  • Good point. I've been doing too much actual functional coding lately. Fixed. – Mark Rendle Jan 31 '11 at 17:48
  • Although you'd have to be retrying a lot before you actually get any performance and memory issues. – Mark Rendle Jan 31 '11 at 17:50
0
while(true)
{

try
{
 Method();
 break;
}
catch(Exception ex)
{
 i++;
 if(i == n) throw ex;
}

}
SiN
  • 3,704
  • 2
  • 31
  • 36
0

Here is a similar code that wraps IO sharing violation. It's the same idea: we have a delegate and a wrapper static method:

/// <summary>
/// Defines a sharing violation wrapper delegate.
/// </summary>
public delegate void WrapSharingViolationsCallback();

/// <summary>
/// Wraps sharing violations that could occur on a file IO operation.
/// </summary>
/// <param name="action">The action to execute. May not be null.</param>
/// <param name="exceptionsCallback">The exceptions callback. May be null.</param>
/// <param name="retryCount">The retry count.</param>
/// <param name="waitTime">The wait time in milliseconds.</param>
public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime)
{
    if (action == null)
        throw new ArgumentNullException("action");

    for (int i = 0; i < retryCount; i++)
    {
        try
        {
            action();
            return;
        }
        catch (IOException ioe)
        {
            if ((IsSharingViolation(ioe)) && (i < (retryCount - 1)))
            {
                bool wait = true;
                if (exceptionsCallback != null)
                {
                    wait = exceptionsCallback(ioe, i, retryCount, waitTime);
                }
                if (wait)
                {
                    Thread.Sleep(waitTime);
                }
            }
            else
            {
                throw;
            }
        }
    }
}

And then, we call it this way (lambda expression suits perfectly here):

    WrapSharingViolations(() => DoWhatever(...));
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

You might be able to do this with a GOTO (gasp)

int count = 0;

TryAgain:
try 
{
   // Do something with your web service
}  
catch(Exception e) 
{
    if(count < numberOfAttemptsAllowed)
    {
         count++;
         goto TryAgain;
    } 
}

I'm sure there might be a better way, but this might do what you need.

Vulume
  • 15
  • 5
Matt
  • 3,664
  • 3
  • 33
  • 39