Click here to Skip to main content
15,892,537 members
Articles / Programming Languages / C# 5.0

Another way for RETRY logic without using GOTO keyword

Rate me:
Please Sign up or sign in to vote.
4.04/5 (9 votes)
28 Apr 2014CPOL2 min read 29.1K   97   10   19
Alternative way for implementing Abort/Retry/Ignore logic

Introduction

The above dialogue should be a quite common error prompt in our daily operation.

If the operation behind is just a simple action like copy one file, to implement the retry logic will be rather simple as well.

But this article will discuss the retry logic within sequential actions. E.g. Copy 10 files, the 5th file copy failed and a message box shown, now user should decide ‘Abort’, ‘Retry’ or ‘Ignore’. And based on user’s response, we now discuss how to implement the ‘Abort’, ‘Retry’ and ‘Ignore’ logic.

Background

Recently just saw such a file copy error dialogue with retry button during some software installation. Suddenly start to think how this should be implemented in a good way. As we all known, a common and a quick way to do retry is via ‘GOTO’ keyword, but this may mess up your logic and your codes. So I’m trying to avoid using ‘GOTO’ but via the recursion approach.

Implement the Logic

Let me show my demo codes first which implement the ‘Abort’, ‘Retry’ and ‘Ignore’ logic:

C#
/// <summary>
/// This is the main function to do the work with retry logic
/// </summary>
/// <param name="jobName">Indicates current job name for displaying in message box</param>
/// <param name="retryCount">Indicates how many retried has been executed</param>
/// <returns>
/// -1: Abort is selected by user in message box
///  0: Ignore is selected by user in message box or there is no error when doing current job
/// </returns>
private int StartWorkProcess(string jobName, int retryCount)
{
    try
    {
        //do something here
        Console.WriteLine("doing job " + jobName + "...");

        //demo an exception when the job name's length equals to 3, 6 and 9
        if (Math.IEEERemainder(jobName.Length, 3)==0)
        {
            throw new Exception();
        }

    }
    catch
    {
        switch (MessageBox.Show("Failed doing job "  + jobName, string.Empty, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error))
        {
            case System.Windows.Forms.DialogResult.Abort:
                return -1;  //Abort all
                break;
            case System.Windows.Forms.DialogResult.Retry:
                if (retryCount > 10)    //Limite the retry times
                {
                    switch (MessageBox.Show("Too many retries! Yes to abort the whole job, No to ignore current task.", string.Empty, MessageBoxButtons.YesNo, MessageBoxIcon.Warning))
                    {
                        case System.Windows.Forms.DialogResult.Yes:
                            return -1;  //Abort all
                            break;
                        case System.Windows.Forms.DialogResult.No:
                            //do nothing more
                            return 0;
                            break;
                    }
                }
                else
                {
                    return StartWorkProcess(jobName, retryCount + 1);
                }
                break;
            case System.Windows.Forms.DialogResult.Ignore:
                //do nothing more
                break;
        }
    }
    return 0;
}

This is the main method to do the work with ‘Abort’, ‘Retry’ and ‘Ignore’ logic.

Here is some key points:

  1. Using a try…catch block to catch any exceptions. Of course, you may extend this to multiple catches to specify certain exceptions.
  2. When there is an exception, and user chooses to retry, this method increase retryCount and calls itself again to redo the same logic for retrying.
  3. A return value is necessary to indicate the status and for the caller function to do its own work.
  4. The number of retrying times is also necessary to be traced and force abort or ignore current task when the number of retrying times exceed the threshold value. This may avoid potential out of memory and stack overflow issue.

And here is the caller function, which will use the return value of the work method:

C#
/// <summary>
/// On OK button click, it will call the work function to do the job.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOK_Click(object sender, EventArgs e)
{
    string job = string.Empty;
    //Simulate 9 jobs with job name 1, 12, 123, 1234 and etc.
    for (int i = 1; i < 10; i++)
    {
        job = job + i.ToString();
        if (StartWorkProcess(job, 0) == -1)
        {
            break;
        }
    }
}

When running the codes, you may monitor result via the program outputs in Visual Studio Output window. Here shows a sample output:

doing job 1...
doing job 12...
doing job 123...
A first chance exception of type 'System.Exception' occurred in RetryIgnoreInRecursion.exe
doing job 123...
doing job 123...
A first chance exception of type 'System.Exception' occurred in RetryIgnoreInRecursion.exe
A first chance exception of type 'System.Exception' occurred in RetryIgnoreInRecursion.exe
doing job 1234...
doing job 12345...
doing job 123456...
doing job 1234567...
doing job 12345678...
doing job 123456789...

Now Do Something Better

Now you should have a basic idea about what I’m talking about and how the logic is implemented in codes. But let us do something better, using the template method pattern to make the codes reusable.

An abstract class is created and basic retry/ignore/abort logic is implemented, while the actual working logic is not implemented but leave it to subclass as an abstract method.

C#
abstract class RetryIgnoreHandlerBase
{
    /// <summary>
    /// Abstract method for subclass to implement actual working logic
    /// </summary>
    protected abstract void DoActualJob(string jobName);

    /// <summary>
    /// This is the main function to do the work with retry logic
    /// </summary>
    /// <param name="jobName">Indicates current job name for displaying in message box</param>
    /// <param name="retryCount">Indicates how many retried has been executed</param>
    /// <returns>
    /// -1: Abort is selected by user in message box
    ///  0: Ignore is selected by user in message box or there is no error when doing current job
    /// </returns>
    public int StartWorkProcess(string jobName, int retryCount)
    {
        try
        {
            DoActualJob(jobName);
        }
        catch
        {
            switch (MessageBox.Show("Failed doing job " + jobName, string.Empty, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error))
            {
                case System.Windows.Forms.DialogResult.Abort:
                    return -1;  //Abort all
                    break;
                case System.Windows.Forms.DialogResult.Retry:
                    if (retryCount > 10)    //Limite the retry times
                    {
                        switch (MessageBox.Show("Too many retries! Yes to abort the whole job, No to ignore current task.", string.Empty, MessageBoxButtons.YesNo, MessageBoxIcon.Warning))
                        {
                            case System.Windows.Forms.DialogResult.Yes:
                                return -1;  //Abort all
                                break;
                            case System.Windows.Forms.DialogResult.No:
                                //do nothing more
                                return 0;
                                break;
                        }
                    }
                    else
                    {
                        return StartWorkProcess(jobName, retryCount + 1);
                    }
                    break;
                case System.Windows.Forms.DialogResult.Ignore:
                    //do nothing more
                    break;
            }
        }
        return 0;
    }
}

The subclass inherit the above abstract class and implement the actual working logic instead:

C#
class DemoRetry: RetryIgnoreHandlerBase
{
    protected override void DoActualJob(string jobName)
    {
        //do something here
        Console.WriteLine("doing job " + jobName + "...");

        //demo an exception when the job name's length equals to 3, 6 and 9
        if (Math.IEEERemainder(jobName.Length, 3) == 0)
        {
            throw new Exception();
        }
    }
}

Then the relevant change to the caller function:

C#
private void btnOK_Click(object sender, EventArgs e)
{
    string job = string.Empty;
    DemoRetry demo = new DemoRetry();

    //Simulate 9 jobs with job name 1, 12, 123, 1234 and etc.
    for (int i = 1; i < 10; i++)
    {
        job = job + i.ToString();
        if (demo.StartWorkProcess(job, 0) == -1)
        {
            break;
        }
    }
}

Your Solution is Welcome

Is there any other solution to implement the abort/retry/ignore logic? Please feel free to leave comments and share with all.

History

April, 2014, Version 1.

License

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


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

Comments and Discussions

 
QuestionThis is the right way to do it. Pin
jgakenhe19-Aug-14 7:05
professionaljgakenhe19-Aug-14 7:05 
GeneralVote of 1 would be too generous Pin
SteveTheThread28-Apr-14 22:08
SteveTheThread28-Apr-14 22:08 
GeneralMy vote of 1 PinPopular
johannesnestler28-Apr-14 10:08
johannesnestler28-Apr-14 10:08 
Hard decision to give such a bad vote...

I want to thank you for writing an article about this topic, but I wouldn't go for your approach in general.

So reason for vote of 1:
* article title: "...without using goto" - I would haved never thought about a solution with goto???
* Why introduce recursion problems (and it's a tail recursion too...) to a problem which can easily be solved by using a do-while loop (did you never create a console "menu"?)
* Abstract base class for retryable operations!? - which depenends on a UI-library (System.Windows.Forms in your case)!? - No.
* Return "magic numbers" (-1 Abort...) - why not use an enum or the DialogResult?
* let's better not talk about async (your operations will most likely be executed on another thread)

Sorry, you see?

But you asked for alternatives:

This would be a solution:
* without goto
* without recursion
* without need to inherit from a base class to implement retry logic (retry/asking etc. logic should be some kind of decorator to any action)
* no dependency to a specific UI-library/Framework (WPF, Forms, Silverlight,...)
* without C/C++ style "magic number" return values Poke tongue | ;-P
* Maximum code reuse.

Something like this:

Let's define a user decision as an enum

C#
public enum UserDecision
 {
     None,
     Retry,
     Ignore,
     Abort
 }


and an Interface to get a decision (most likely implemented by a UI-Control, e.g. a Form)

C#
public interface IUserDecisionProvider
{
    UserDecision GetUserDecision(string strMessage, string strTitle);
}


So your Operation-class could start something like this (just quick code to get the idea)

C#
public class RetryableOperation
{
    IUserDecisionProvider m_userdecisionprovider;
    Action m_action;
    int m_iMaxRetries = 1;

    public RetryableOperation(Action action, IUserDecisionProvider userdecisionprovider, int iMaxRetries = 1)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        if (userdecisionprovider == null)
            throw new ArgumentNullException("userdecisionprovider");

        m_action = action;
        m_userdecisionprovider = userdecisionprovider;
        m_iMaxRetries = iMaxRetries;
    }

    public void Run()
    {
        UserDecision decisionLast = UserDecision.None;
        int iRetryCount = 0;
        bool bCancel = false;

        do
        {
            try
            {
                m_action.Invoke();
            }
            catch (Exception ex)
            {
                decisionLast = m_userdecisionprovider.GetUserDecision(ex.Message, "Error");
                switch (decisionLast)
                {
                    case UserDecision.Retry:
                        iRetryCount++;
                        break;
                    case UserDecision.Ignore:
                        bCancel = true;
                        break;
                    case UserDecision.Abort:
                        bCancel = true;
                        break;
                    default:
                        break;
                }
            }
        } while (!bCancel && iRetryCount < m_iMaxRetries);
    }
}


the idea is to create a class to bind the executed Action (or should it be Task?) together with an "decision" Provider for asking the user what to do. I think you can see the pattern, - inject the dependencies, then do in your "Run" implementation what ever you want.(other cancel logic, more enum states (AbortAll, IgnoreAll, ...), make the execution async, grouped, parallel, whatever...).
My Run mehtod is just a "dumb" example - but at least it does't need a goto or recursion...

A quick example how to use the RetryableOperation :
public class SampleForm : Form, IUserDecisionProvider
 {
     public SampleForm()
     {
         Button buttonRun = new Button { Text = "Run", Parent = this };
         buttonRun.Click += (object sender, EventArgs e) =>
             {
                 Action actionSample = () => { throw new NotImplementedException(); };
                 RetryableOperation operationSample = new RetryableOperation(actionSample, this, 5);
                 operationSample.Run();
             };
     }


     public UserDecision GetUserDecision(string strMessage, string strCaption)
     {
         DialogResult dialogresult = MessageBox.Show(strMessage, strCaption, MessageBoxButtons.AbortRetryIgnore);

         switch (dialogresult)
         {
             case DialogResult.Abort:
                 return UserDecision.Abort;
             case DialogResult.Ignore:
                 return UserDecision.Ignore;
             case DialogResult.Retry:
                 return UserDecision.Retry;
             default:
                 return UserDecision.None;
         }
     }
 }


If you try it this way, you can "retry/abort/cancel" any existing Action you have - on exception. If you like the approach, maybe we can together create a more "polished" version of the code with batch execution (maybe a List of Tasks? and async execution?
You could update your article, or we post an alternative...

Kind regards Johannes
Question不错 Pin
Member 1020865924-Apr-14 14:19
Member 1020865924-Apr-14 14:19 
AnswerRe: 不错 Pin
Nelek28-Apr-14 5:00
protectorNelek28-Apr-14 5:00 
QuestionHow about this code? Pin
FatCatProgrammer24-Apr-14 6:59
FatCatProgrammer24-Apr-14 6:59 
AnswerRe: How about this code? Pin
John Brett25-Apr-14 5:30
John Brett25-Apr-14 5:30 
GeneralRe: How about this code? Pin
FatCatProgrammer25-Apr-14 6:58
FatCatProgrammer25-Apr-14 6:58 
BugMissing picture at top Pin
James Jensen24-Apr-14 4:01
professionalJames Jensen24-Apr-14 4:01 
GeneralRe: Missing picture at top Pin
TonyTonyQ24-Apr-14 5:08
professionalTonyTonyQ24-Apr-14 5:08 
GeneralRe: Missing picture at top Pin
Ravi Bhavnani24-Apr-14 5:52
professionalRavi Bhavnani24-Apr-14 5:52 
SuggestionConsider use of Actions Pin
Henrik Jonsson24-Apr-14 3:07
Henrik Jonsson24-Apr-14 3:07 
QuestionWhy not just use GOTO ?? Pin
Citiga24-Apr-14 2:22
Citiga24-Apr-14 2:22 
AnswerRe: Why not just use GOTO ?? Pin
John Brett24-Apr-14 3:33
John Brett24-Apr-14 3:33 
AnswerRe: Why not just use GOTO ?? Pin
PIEBALDconsult24-Apr-14 3:56
mvePIEBALDconsult24-Apr-14 3:56 
AnswerRe: Why not just use GOTO ?? Pin
CHill6024-Apr-14 5:40
mveCHill6024-Apr-14 5:40 
AnswerRe: Why not just use GOTO ?? Pin
Victoria TO25-Apr-14 0:50
Victoria TO25-Apr-14 0:50 
AnswerRe: Why not just use GOTO ?? Pin
rxantos1-May-14 16:00
rxantos1-May-14 16:00 
QuestionHow about a do ... while (true)? Pin
Vasudevan Deepak Kumar24-Apr-14 1:44
Vasudevan Deepak Kumar24-Apr-14 1:44 

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.