Back to: C#.NET Tutorials For Beginners and Professionals
Retry Pattern in C# using Asynchronous Programming with Examples
In this article, I am going to discuss Retry Pattern in C# using Asynchronous Programming with Examples. Please read our previous article where we discussed How to Create Synchronous Method using Task in C# with Examples.
Retry Pattern in C# using Asynchronous Programming
One application of Asynchronous Programming is to perform a Retry Pattern. The idea is that sometimes there will be operations that we want to retry several times. However, we do not want to retry immediately, but we want to retry after a certain amount of time. For example, if we make an HTTP request to a Web server, sometimes those operations failed and we may not want immediately tell the user that there was an error. We may want to retry the operation just in case the operation works this time.
Structure of Retry Pattern in C#:
The following image shows the basic structure of the Retry Pattern in C# Asynchronous Programming.
Here, the variable RetryTimes tells the number of times we will retry the operation if it is failing. If it is not failing then we will not retry. And, we have set the value to 3 which means it will retry the operation maximum of 3 times.
And one more thing, we don’t want to immediately retry the operation. We may want to retry the operation after a certain amount of time. Here, the parameter WaitTime specifies the time duration for retrying the operation. We have set the value of WaitTime to 500 milliseconds, so it will retry the operation after 500 milliseconds or half a second.
Then we created the for loop using the try-catch block. This for loop will execute a minimum of 1 time and a maximum of 3 times as we set the RetryTimes value as 3.
Then inside the try block, we will call our async operation. The operation may be an API call or an Async method call. If the operation is successful, we will break the loop and will come out from the for loop. If the operation is not successful meaning if we are getting any exception from the API, or from the Async method (whatever may be the operation), then the catch block will handle that exception and executes the catch block. If you want, then you can log the exception details, then wait for 500 milliseconds before continuing the next iteration of the loop.
Example to understand Retry Pattern in C#:
The following example shows the Retry Pattern in C# using Asynchronous Programming.
using System; using System.Threading.Tasks; namespace AsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine("Main Method Started"); RetryMethod(); Console.WriteLine("Main Method Completed"); Console.ReadKey(); } public static async void RetryMethod() { //It tells the number of times we will retry the operation if it is failing //Of course, if it is not falling then we will not retry var RetryTimes = 3; //The idea is that we don't want to immediately retry, but //we may want to retry after a certain amount of time. //In our case, it is five hundred milliseconds or half a second. var WaitTime = 500; for (int i = 0; i < RetryTimes; i++) { try { //Do the Operation //If the Operation Successful break the loop await RetryOperation(); Console.WriteLine("Operation Successful"); break; } catch (Exception Ex) { //If the operations throws an error //Log the Exception if you want Console.WriteLine($"Retry {i+1}: Getting Exception : {Ex.Message}"); //Wait for 500 milliseconds await Task.Delay(WaitTime); } } } public static async Task RetryOperation() { //Doing Some Processing await Task.Delay(500); //Throwing Exception so that retry will work throw new Exception("Exception Occurred in while Processing..."); } } }
Output:
Generic Retry Pattern in C# Asynchronous Programming:
In the previous example, we have seen how to create Retry Pattern in Asynchronous Programming. If we want to apply Retry Pattern at multiple places, then we need to make the retry pattern generic. Let us see how we can do this. Please have a look at the following image.
The above retry pattern exactly does the same thing as the previous one. The only difference here is that this retry pattern can be used with multiple methods. Let us see an example for understanding. Please have a look at the below example. In the below example, using the generic retry pattern we are invoking the RetryOperation1 and RetryOperation2 async methods.
using System; using System.Threading.Tasks; namespace AsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine("Main Method Started"); RetryMethod(); Console.WriteLine("Main Method Completed"); Console.ReadKey(); } public static async void RetryMethod() { //It will retry 3 times, here the function is RetryOperation1 await Retry(RetryOperation1); //It will retry 4 times, here the function is RetryOperation2 await Retry(RetryOperation2,4); } //Generic Retry Method //Func is a generate delegate which returns something, in our case it is returning a Task //We are setting the default value for RetryTimes = 3 and WaitTime = 500 milliseconds public static async Task Retry(Func<Task> fun, int RetryTimes = 3, int WaitTime = 500) { for (int i = 0; i < RetryTimes; i++) { try { //Do the Operation //We are going to invoke whatever function the generic func delegate points to await fun(); Console.WriteLine("Operation Successful"); break; } catch (Exception Ex) { //If the operations throws an error //Log the Exception if you want Console.WriteLine($"Retry {i + 1}: Getting Exception : {Ex.Message}"); //Wait for 500 milliseconds await Task.Delay(WaitTime); } } } public static async Task RetryOperation1() { //Doing Some Processing await Task.Delay(500); //Throwing Exception so that retry will work throw new Exception("Exception Occurred in RetryOperation1"); } public static async Task RetryOperation2() { //Doing Some Processing await Task.Delay(500); //Throwing Exception so that retry will work throw new Exception("Exception Occurred in RetryOperation2"); } } }
Output:
Problems with the above Generic Retry Pattern?
In the above generic retry pattern, we have one problem. We are calling the Retry from the RetryMethod as follows:
await Retry(RetryOperation1);
Here, what if I want to do something if the operation fails three times? Because of the way we implemented the generic Retry method it just continues executing without telling us that the operation was successful or there was an error. Let us modify the Retry method as follows. Here, we are reducing the for loop execution for 1 time, so that we can execute the operation last time outside the for loop and this will work.
In the above code, we will get the RetryTimes value as 3, then the loop will execute 2 times if the operation was not successful. The last time will execute outside the for loop and we are not handling the exception here, so it will throw an exception that will tell the operation was successful. Now, you can catch the exception from where you called the Retry method as follows:
The complete example code is given below.
using System; using System.Threading.Tasks; namespace AsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine("Main Method Started"); RetryMethod(); Console.WriteLine("Main Method Completed"); Console.ReadKey(); } public static async void RetryMethod() { //It will retry 3 times, here the function is RetryOperation1 try { await Retry(RetryOperation1); } catch(Exception ex) { Console.WriteLine("The Operation was Failed"); } } //Generic Retry Method //Func is a generate delegate which returns something, in our case it is returning a Task //We are setting the default value for RetryTimes = 3 and WaitTime = 500 milliseconds public static async Task Retry(Func<Task> fun, int RetryTimes = 3, int WaitTime = 500) { //Reducing the for loop Exection for 1 time for (int i = 0; i < RetryTimes - 1; i++) { try { //Do the Operation //We are going to invoke whatever function the generic func delegate points to await fun(); Console.WriteLine("Operation Successful"); break; } catch (Exception Ex) { //If the operations throws an error //Log the Exception if you want Console.WriteLine($"Retry {i + 1}: Getting Exception : {Ex.Message}"); //Wait for 500 milliseconds await Task.Delay(WaitTime); } } //Final try to execute the operation await fun(); } public static async Task RetryOperation1() { //Doing Some Processing await Task.Delay(500); //Throwing Exception so that retry will work throw new Exception("Exception Occurred in RetryOperation1"); } } }
When you run the above code, you will get the following output. Here, you can see that we get two errors because the loop executes two times, and finally, we get that operation failed. This is because the final execution of the function is executed outside of the try-catch block.
Generic Async Retry Method with Returning Value in C#:
As of now, the way we implement the Generic Retry method is not returning any value. Now, let us create a generic Retry method to return a value. If you want to return a value then you need to use Task<T>. For a better understanding, please have a look at the below image. In the below, T represents the type of value that the operation is going to return.
In order to test the above Retry method, please create the following async method which returns a string.
public static async Task<string> RetryOperationValueReturning() { //Doing Some Processing and return the value await Task.Delay(500); //Throwing Exception so that retry will work throw new Exception("Exception Occurred in RetryOperation1"); }
The Complete Example code is given below.
using System; using System.Threading.Tasks; namespace AsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine("Main Method Started"); RetryMethod(); Console.WriteLine("Main Method Completed"); Console.ReadKey(); } public static async void RetryMethod() { //It will retry 3 times, here the function is RetryOperation1 try { var result = await Retry(RetryOperationValueReturning); Console.WriteLine(result); } catch(Exception ex) { Console.WriteLine("The Operation was Failed"); } } //Generic Retry Method Returning Value //Func is a generate delegate which returns something, in our case it is returning a Task //We are setting the default value for RetryTimes = 3 and WaitTime = 500 milliseconds public static async Task<T> Retry<T>(Func<Task<T>> fun, int RetryTimes = 3, int WaitTime = 500) { //Reducing the for loop Exection for 1 time for (int i = 0; i < RetryTimes - 1; i++) { try { //Do the Operation //We are going to invoke whatever function the generic func delegate points to //We will return from here if the operation was successful return await fun(); } catch (Exception Ex) { //If the operations throws an error //Log the Exception if you want Console.WriteLine($"Retry {i + 1}: Getting Exception : {Ex.Message}"); //Wait for 500 milliseconds await Task.Delay(WaitTime); } } //Final try to execute the operation return await fun(); } public static async Task<string> RetryOperationValueReturning() { //Doing Some Processing and return the value await Task.Delay(500); //Uncomment the below code to successfully return a string //return "Operation Successful"; //Throwing Exception so that retry will work throw new Exception("Exception Occurred in RetryOperation1"); } } }
Output:
The following code will be successfully run.
using System; using System.Threading.Tasks; namespace AsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine("Main Method Started"); RetryMethod(); Console.WriteLine("Main Method Completed"); Console.ReadKey(); } public static async void RetryMethod() { try { var result = await Retry(RetryOperationValueReturning); Console.WriteLine(result); } catch(Exception ex) { Console.WriteLine("The Operation was Failed"); } } public static async Task<T> Retry<T>(Func<Task<T>> fun, int RetryTimes = 3, int WaitTime = 500) { for (int i = 0; i < RetryTimes - 1; i++) { try { return await fun(); } catch (Exception Ex) { Console.WriteLine($"Retry {i + 1}: Getting Exception : {Ex.Message}"); await Task.Delay(WaitTime); } } return await fun(); } public static async Task<string> RetryOperationValueReturning() { await Task.Delay(500); return "Operation Successful"; } } }
Output:
So, we have implemented a Retry Pattern that allows us to centralize the logic of repeating an operation several times until it works or until we run out of retry trials.
In the next article, I am going to discuss the Only One Pattern in C# Asynchronous Programming with Examples. Here, in this article, I try to explain Retry Pattern in C# Asynchronous Programming with Examples. I hope you enjoy this Retry Pattern in C# using Asynchronous Programming with Examples article.
a little mistake in the “Problems with the above Generic Retry Pattern?” code.
Inside the for loop, if the task succeeds, we must return and not break. Because if it succeeds, it breaks the for loop, but the task is redone outside the for loop (the code is good in the generic code example though, because we can clearly see that it returns if the task succeeds)
If the task was successfully executed on the third attempt, we don’t see that…