Trying and Retrying in C#
I sometimes encounter a requirement where we want to execute some code and, if that method throws an exception, we want to retry executing it several times until some limit is reached. In this scenario, the thrown exception was expected at times by the application, and could be ignored. The operation was something that failed frequently enough that it was necessary sometimes to retry until it succeeded (web services calls for example may fail due to intermittent connectivity issues).
Another scenario was that if the code failed, we wanted to wait a period of time before retrying. Below is what I came up with.
1: var service = new WebService();
2: //the type parameter is for the return type of
3: //the method we are retrying (GetDataFromRemoteServer()
4: //returns a string in this example)
5: var retrier = new Retrier<string>();
6: //call the service up to 3 times in the event of failure
7: string result = retrier.Try(
8: () => service.GetDataFromRemoteServer(), 3);
9:
10: //call the service up to 3 times,
11: //wait 500ms if there is a failure
12: string result2 = retrier.TryWithDelay(
13: ()=> service.GetDataFromRemoteServer(), 3, 500);
And here is the Retrier class. It takes a lambda expression, tries to execute it, and if an exception is thrown, it swallows it and retries executing the code until maxRetries is reached. This is only useful in scenarios where the exception that gets thrown is expected from time to time, and you just want to ignore it and re-execute your code. One way to improve would be to take as a parameter an ExpectedException type, and if the caught exception does not match that, throw. You should never swallow exceptional exceptions. Also, if you try to execute the method the maximum number or retry times, and it is never successful, you probably want to go ahead and rethrow the exception in that case. One scenario I think this might be useful is calling web services where the network connection is prone to failure, thus you want to retry in the event of a failure.
1: public class Retrier<TResult>
2: {
3: public TResult Try(Func<TResult> func,
4: int maxRetries)
5: {
6: TResult returnValue = default(TResult);
7: int numTries = 0;
8: bool succeeded = false;
9: while (numTries < maxRetries)
10: {
11: try
12: {
13: returnValue = func();
14: succeeded = true;
15: }
16: catch (Exception)
17: {
18: //todo: figure out what to do here
19: }
20: finally
21: {
22: numTries++;
23: }
24: if (succeeded)
25: return returnValue;
26: }
27: return default(TResult);
28: }
29:
30: public TResult TryWithDelay(Func<TResult> func, int maxRetries,
31: int delayInMilliseconds)
32: {
33: TResult returnValue = default(TResult);
34: int numTries = 0;
35: bool succeeded = false;
36: while (numTries < maxRetries)
37: {
38: try
39: {
40: returnValue = func();
41: succeeded = true;
42: }
43: catch (Exception)
44: {
45: //todo: figure out what to do here
46: }
47: finally
48: {
49: numTries++;
50: }
51: if (succeeded)
52: return returnValue;
53: System.Threading.Thread.Sleep(delayInMilliseconds);
54: }
55: return default(TResult);
56: }
57: }
Comments
Anonymous
March 09, 2010
Any reason you didn't define the Try method this way: public TResult Try(Func<TResult> func, int maxRetries) { return TryWithDelay(func, maxRetries, 0); }Anonymous
March 09, 2010
Nope. I like your way better. Thanks for commenting.Anonymous
March 09, 2010
My pleasure! This has been bookmarked and will be used in my next project (with due credit, of course!). Wonder if something like this could be incorporated directly into System.Threading.Anonymous
March 11, 2010
I had implemented something very similar. My implementation has some additional extensibility points around if and when you should retry and now long. If your solution, you retry regardless of what exception cause the error. When calling a data access code, do you really want sleep and retry if NullReferenceException or StackOverflowException is called? Sleeping a static time can also cause problems. If your Exception is being thrown due to a database deadlock, and two different threads both are retrying and waiting the same amount of time, the deadlock could reoccur. Take a read of my blog post and review the code. You may find it useful. http://philbolduc.blogspot.com/2010/03/retryable-actions-in-c.htmlAnonymous
March 12, 2010
Hi Phil. I like the flexibility and robustness of your solution. In the post I mentioned it would be a good idea to take an Expected Exception as a parameter, and only swallow if the expected (presumably innocuous) exception occcurs, and it looks like you've implemented that. Nice. Thanks for commenting!Anonymous
December 09, 2013
More generic logic for retry for expected output value: public TResult Try(Func<TResult> func, int maxRetries, TResult expectedValue, int delayInMilliseconds = 0) { TResult returnValue = default(TResult); int numTries = 0; bool succeeded = false; while (numTries < maxRetries) { try { returnValue = func(); succeeded = returnValue.Equals(expectedValue); } catch (Exception) { //todo: figure out what to do here } finally { numTries++; } if (succeeded) return returnValue; System.Threading.Thread.Sleep(delayInMilliseconds); } return default(TResult); }Anonymous
May 11, 2014
Anyway i can pass in function with void TResult to the function? There is no return result from my function as it was a process. Please advise how to code.