Click here to Skip to main content
15,886,049 members
Articles / Programming Languages / C#
Article

A Class for Retry Management

Rate me:
Please Sign up or sign in to vote.
4.75/5 (4 votes)
6 Nov 2008CPOL2 min read 38.5K   280   54   7
Allows the developer to use a built-in retry machanism, instead of having to develop it.
ClassDiagram.jpg

Introduction

On numerous occasions, it becomes necessary for a developer to perform retries. For example, when an application tries to access the DB to read data, or read from a file. We'll need our application to be able to retry, based on our own parameters, until it is successful, or until we want it to stop.

Using the Code

The library is a DLL which can be used in any application. It allows to specify a retry schedule. For example:

  1. Retry 20 times, every 5 seconds
  2. Retry 30 times, every minute
  3. Retry forever, every hour

Such a retry schedule will enable the application to retry until it succeeds. Building a schedule this way allows the developer to make sure that the network/CPU is not overloaded by retries for a long time. It is possible to specify multiple schedules. Each will follow the other when the previous one completes.

The retry manager accepts a delegate to a method (Thanks to Ramon for the tip). This delegate will call a function in the calling application. If that delegate returns false or throws an exception, the retry manager will retry based on the schedule defined.

Each retry action also raises an event to the calling application, with the retry data. This allows the developer to log the retry events, notify the user, and manage them.

The attached console application tries to perform an activity with two retry schedules. As shown, it will first try to do something. After failure, it will retry three times every second. If it fails, it will try every 2 seconds, indefinitely.

TestConsole.jpg
C#
static RetryTimer.RetryManager manager = new RetryTimer.RetryManager();
static void Main(string[] args)
{
    Console.WriteLine("Trying...");
            if (!TrySomething())
            {
                Console.WriteLine("Failed, Retrying");
                //The action we tried did not succeed
                //Add an event handler to the retry mechanism
                manager.DoRetry += new RetryTimer.RetryHandler(manager_DoRetry);
                //add 2 schedules
                //Try 5 times every 1 second
                manager.AddSchedule(RetryTimer.MeasurementUnit.Seconds, 3, 1);
                //If still unsuccessful - try indefinitely every 10 seconds
                manager.AddSchedule(RetryTimer.MeasurementUnit.Seconds, -1, 2);
                //Start Retry
                RetryTimer.RetryCallback handler = TrySomething;
                manager.Start(handler);
                Console.ReadLine();
            }
            else
            {
                //The actions succeeded
                Console.WriteLine("Success!");
            }
}

The retry event handler handles retrying and reports the results of the retry to the retry manager.

C#
static void manager_DoRetry(object sender, RetryTimer.DoRetryEventArgs args)
{
  Result = (string.Format("Retrying {0} of {1} every {2} {3} at {4}",
                                          new object[] {args.CurrentRetry,
                                          args.CurrentSchedule.RetryCount,
                                          args.CurrentSchedule.UnitCount,
                                          args.CurrentSchedule.UnitOfMeasure.ToString(),
                                          DateTime.Now.ToString()}));
    Console.WriteLine(Result);
}

The retry manager will call the delegate based on the schedule, until stopped or until the schedules are completed.

C#
public void Start(RetryCallback callback)
        {
            StopRequested = false;
            if (mScheduleList.Count == 0)
            {
                throw new Exception("No Schedules defined. Cannot Start");
            }
            bool successTry = false;
            //loop through all schedules
            while (currentScheduleIndex <= (mScheduleList.Count - 1) && 
			!successTry && !StopRequested)
            {
                while ((ElapsedCount <= mScheduleList[currentScheduleIndex].RetryCount ||
                    mScheduleList[currentScheduleIndex].RetryCount<0) && 
		!successTry && !StopRequested)
                {
                    try
                    {
                        if (callback())
                        {
                            successTry = true; ;
                        }
                    }
                    catch (Exception ex)
                    {

                        //throw;
                    }
                    if (DoRetry != null)
                    {
                        DoRetry(this, new DoRetryEventArgs
			(mScheduleList[currentScheduleIndex], ElapsedCount));
                    }
                    if (!successTry)
                    {
                        for (int numberOfUnits = 0;
                            numberOfUnits < mScheduleList
				[currentScheduleIndex].UnitCount *
                           Convert.ToUInt32( mScheduleList
				[currentScheduleIndex].UnitOfMeasure) / 1000;
                            numberOfUnits++)
                        {
                            System.Threading.Thread.Sleep(1000);
                        }
                    }

                    ElapsedCount++;
                }
                currentScheduleIndex++;
                ElapsedCount = 1;
            }
        }

History

  • 3rd November, 2008 - Initial release of the article
  • 5th November, 2008 - Some changes and updates - especially using a delegate

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGood article Pin
Donsw22-Jan-09 16:59
Donsw22-Jan-09 16:59 
General"Console.ReadLine();" in "manager_DoRetry(...)" Pin
NorbertKl175-Nov-08 1:51
NorbertKl175-Nov-08 1:51 
GeneralRe: "Console.ReadLine();" in "manager_DoRetry(...)" Pin
Rotem Sapir6-Nov-08 3:28
Rotem Sapir6-Nov-08 3:28 
GeneralRedundancy, reliability, etc. Pin
Dmitri Nеstеruk4-Nov-08 5:22
Dmitri Nеstеruk4-Nov-08 5:22 
GeneralRe: Redundancy, reliability, etc. Pin
Rotem Sapir6-Nov-08 3:27
Rotem Sapir6-Nov-08 3:27 
GeneralThe following helper class does this too Pin
Ramon Smits3-Nov-08 1:50
Ramon Smits3-Nov-08 1:50 
I didn't take a look at your source code but I created the following helper class years ago. It allows you to easily add a retry mechanism to a statement:

Retry.Delegate statement = delegate { Console.WriteLine("Fire"); throw new Exception("Error"); };

Retry.Do<<exception>(
 5, // Retry count
 1000, // Minimum time for retry
 5000, // Maximum time for retry
 statement); // What code to retry
</exception>

This can ofcourse be done in a combined statement but written this way to easily see how it works.

By the way... I seriously am against retry mechanisms in back-end code! Back-ends should never retry unless it is an automated process and even then you should be very carefull with such designs.

/// <summary>
/// Helper class to make it more easy to retry actions like for example webrequests.
/// </summary>
public static class Retry
{
    private static readonly Random _randomSleep = new Random();

    /// <summary>
    /// Delegate to be used by implementation that need a retry mechanism.
    /// </summary>
    public delegate void Delegate();

    /// <summary>
    /// Perform an operation and when it fails (exception occurs) then retry it a couple of times with a configured delay in between.
    /// </summary>
    /// <typeparam name="T">The exception type that is allowed to occur.</typeparam>
    /// <param name="count">Maximum retry count</param>
    /// <param name="minWait">Minimum delay before a new attemp is made.</param>
    /// <param name="maxWait">Maximum delay before a new attemp is made.</param>
    /// <param name="method">Method to call.
    /// <remarks>
    /// The minimum and maximum wait arguments are there so that clients will not retry an operation all at the same time which could result in yet again unnecessary failure. This is especially usefull for retrying operations over the network especially in combination with <see cref="T"/> to specify <see cref="WebException"/>.
    /// </remarks>
    public static void Do<T>(
        int count,
        int minWait,
        int maxWait,
        Delegate method
        ) where T : Exception
    {
    start:
        --count;
        try
        {
            method();
        }
        catch (T)
        {
            if (count == 0)
            {
                throw;
            }

            if (maxWait > 0)
            {
                Thread.Sleep(_randomSleep.Next(minWait, maxWait));
            }

            goto start;
        }
    }

    /// <summary>
    /// Perform an operation and when it fails (exception occurs) then retry it a couple of times.
    /// </summary>
    /// <param name="count">Maximum retry count</param>
    /// <param name="method">Method to call.</param>
    public static void Do(int count, Delegate method)
    {
        Do<Exception>(count, 0, 0, method);
    }
}

GeneralRe: The following helper class does this too Pin
Rotem Sapir3-Nov-08 2:19
Rotem Sapir3-Nov-08 2:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.