Although some would argue that Exceptions should be thrown and handled one level up. I tend to think there are plenty of cases where by after an Exception is thrown it makes total sense to Retry the operation N number of times before finally Re-throwing the Exception up the Call stack.
One of these such cases is the simple act of Downloading a file from a Remote Server. Although the code is very simple, WebClient.DownloadFile can and does fail frequently for a host of different reasons.
Consider the following example which simply tries to download a remote Uri to a local file path.
private int maxRetries = 5; private int retriesCounter; public void Download(string uri, string targetFilePath) { try { var webClient = new WebClient(); webClient.DownloadFile(uri, targetFilePath); retriesCounter = 0; } catch (WebException webEx) { if (retriesCounter >= maxRetries) { throw; } retriesCounter++; Download(uri, targetFilePath); } }
As you can see with a little bit of Recursion we retry the Operation a maximum of 5 times and then finally Throw the exception.
This type of code is messy to read, very repetitive and also common to errors which can lead to the dreaded StackOverflowException.
To get around this I’ve come up with what I call a ReliableActionInvoker which essentially provides a generic way to Invoke System.Actions and retry them if any Exception or an explicit Exception occurs.
Interface
public interface IReliableActionInvoker { /// <summary> /// Tries to Invoke the supplied action and if any Exception is thrown will /// retry the number of times supplied as maxRetries /// </summary> /// <param name="action"></param> /// <param name="maxRetries"></param> void Invoke(System.Action action, int maxRetries); /// <summary> /// Tries to Invoke the supplied Action and if the Exception is of the /// supplied ExceptionType will retry the number of times supplied as /// maxRetries /// </summary> /// <param name="action"></param> /// <param name="maxRetries"></param> /// <param name="exceptionType"></param> void Invoke(System.Action action, int maxRetries, Type exceptionType); /// <summary> /// Tries to Invoke the supplied Action and if the Action throws /// an Exception that is contained in the supplied exceptionTypes array /// will retry the number of times supplied as maxRetries /// </summary> /// <param name="action"></param> /// <param name="maxRetries"></param> /// <param name="exceptionTypes"></param> void Invoke(System.Action action, int maxRetries, Type[] exceptionTypes); }
If you’re not familiar with System.Action then I suggest you check it out on MSDN.
Implementation
public class ReliableActionInvoker : IReliableActionInvoker { private int retriesCounter; public void Invoke(Action action, int maxRetries) { var exceptionTypes = new Type[0]; Invoke(action, maxRetries, exceptionTypes); } public void Invoke(Action action, int maxRetries, Type exceptionType) { Invoke(action, maxRetries, new[] {exceptionType}); } public void Invoke(Action action, int maxRetries, Type[] exceptionTypes) { try { action.Invoke(); retriesCounter = 0; } catch (Exception ex) { if (retriesCounter >= maxRetries) { retriesCounter = 0; throw; } if (exceptionTypes == null || exceptionTypes.Length == 0) { retriesCounter++; Invoke(action, maxRetries, exceptionTypes); return; } if (exceptionTypes.Contains(ex.GetType())) { retriesCounter++; Invoke(action, maxRetries, exceptionTypes); return; } throw; } } }
This code is pretty self explanatory. I first call Action.Invoke and catch the Exception. When no Exception Type is supplied then Retry, otherwise check if the caught Exception exists in the supplied Collection and Retry.
When the maxRetries has been reached Throw the Exception.
Usage
public class WebClientDownloader : IUriDownloader { private readonly IReliableActionInvoker reliableActionInvoker; public WebClientDownloader(IReliableActionInvoker reliableActionInvoker) { this.reliableActionInvoker = reliableActionInvoker; } public void Download(string uri, string targetFilePath) { uri.ValidateArgumentNull(); targetFilePath.ValidateArgumentNull(); var webClient = new WebClient(); reliableActionInvoker .Invoke(() => webClient.DownloadFile(uri, targetFilePath), 5, typeof(WebException)); } }
Finally here’s how you could use it.
That’s all there is to it, it’s pretty simple really. I’m keen here to what you think or how you’ve approached this problem in a generic way before so feel free to comment.
The nice thing about this is it opens up the possibility to implement this using AOP but I’ll leave that to another post.
Till next time.