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:
private int StartWorkProcess(string jobName, int retryCount)
{
try
{
Console.WriteLine("doing job " + jobName + "...");
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;
break;
case System.Windows.Forms.DialogResult.Retry:
if (retryCount > 10)
{
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;
break;
case System.Windows.Forms.DialogResult.No:
return 0;
break;
}
}
else
{
return StartWorkProcess(jobName, retryCount + 1);
}
break;
case System.Windows.Forms.DialogResult.Ignore:
break;
}
}
return 0;
}
This is the main method to do the work with ‘Abort’, ‘Retry’ and ‘Ignore’ logic.
Here is some key points:
- Using a try…catch block to catch any exceptions. Of course, you may extend this to multiple catches to specify certain exceptions.
- 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.
- A return value is necessary to indicate the status and for the caller function to do its own work.
- 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:
private void btnOK_Click(object sender, EventArgs e)
{
string job = string.Empty;
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.
abstract class RetryIgnoreHandlerBase
{
protected abstract void DoActualJob(string jobName);
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;
break;
case System.Windows.Forms.DialogResult.Retry:
if (retryCount > 10)
{
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;
break;
case System.Windows.Forms.DialogResult.No:
return 0;
break;
}
}
else
{
return StartWorkProcess(jobName, retryCount + 1);
}
break;
case System.Windows.Forms.DialogResult.Ignore:
break;
}
}
return 0;
}
}
The subclass inherit the above abstract class and implement the actual working logic instead:
class DemoRetry: RetryIgnoreHandlerBase
{
protected override void DoActualJob(string jobName)
{
Console.WriteLine("doing job " + jobName + "...");
if (Math.IEEERemainder(jobName.Length, 3) == 0)
{
throw new Exception();
}
}
}
Then the relevant change to the caller function:
private void btnOK_Click(object sender, EventArgs e)
{
string job = string.Empty;
DemoRetry demo = new DemoRetry();
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.