Retry Utility

21 October 2010

This morning I ran into a situation where I needed to retry several method calls if they failed. I decided that the easiest and cleanest way to accomplish this would be via a generic utility class. Below is the class I wrote. In the future I may add a method that will handle targets with void returns. But, for now, this is all my application required.

 1 public class RetryUtil
 2 {
 3   private static readonly log4net.ILog _log = 
 4     log4net.LogManager.GetLogger(typeof(RetryUtil));
 5 
 6   /// <summary>
 7   ///   Executes the specified function.
 8   /// </summary>
 9   /// <typeparam name="T">
10   ///   Type that the target funtion returns.
11   /// </typeparam>
12   /// <param name="func">The target function.</param>
13   /// <param name="requiresRetry">
14   ///   Function that validates whether a retry is required 
15   ///   based on the target function's output.
16   /// </param>
17   /// <param name="retryCount">
18   ///   The maximum number of times the target function 
19   ///   should be executed.
20   /// </param>
21   /// <param name="sleepSeconds">
22   /// The number of seconds to sleep the thread between retries.
23   /// </param>
24   /// <returns>The result of the target function.</returns>
25   public T Execute<T>(Func<T> func, Func<T, bool> requiresRetry, 
26       int retryCount, int sleepSeconds)
27   {
28     var attempt = 0;
29     while (attempt < retryCount)
30     {
31       try
32       {
33         _log.InfoFormat("Attempting call {0} of {1}", 
34           attempt + 1, retryCount);
35         var t = func();
36 
37         if (requiresRetry(t))
38         {
39           attempt++;
40           if (attempt >= retryCount)
41             return t;
42 
43           _log.WarnFormat("Method returned {0}. Retrying.", t);
44 
45           if (sleepSeconds > 0)
46           {
47             _log.InfoFormat("Sleeping thread for {0} seconds.", 
48               sleepSeconds);
49             Thread.Sleep(TimeSpan.FromSeconds(sleepSeconds));
50           }
51           continue;
52         }
53 
54         return t;
55       }
56       catch (Exception ex)
57       {
58         attempt++;
59         _log.Error(ex);
60 
61         if (attempt >= retryCount)
62           throw;
63       }
64     }
65     return default(T);
66   }
67 }

To call this utility, simply pass in a delegate (or lambda expression) that wraps your target method, a delegate (or lambda expression) that validates the return, a retry count, and the number of seconds you want to sleep the thread between retries.

 1 public int TargetMethod(int foo)
 2 {
 3   //Do work..
 4   return -1;
 5 }
 6 
 7 public void CallingMethod()
 8 {
 9   var retryUtil = new RetryUtil();
10 
11   //Example call using delegates
12   Func<int> target = delegate { return MyTargetMethod(123); };
13   Func<int, bool> validate = delegate(int foo) { return foo < 0; };
14   var result1 = retryUtil.Execute(target, validate, 5, 10);
15 
16   //Example using lambda expressions
17   var result2 = retryUtil.Execute(() => MyTargetMethod(123), 
18       (i=> i < 0), 5, 10);
19 }