bloggrammer logo

John Ansa

C# .NET | MERN | DevOps | LLMs
Founder @AlgoCollab
Contact
Skills
Share: 

How to Implement Retry Logic in C#

Recovering from Transient Failures .NET Applications

When developing applications, errors are inevitable. Whether it's a server outage, a database connection failure, or a timeout issue, errors can cause frustration for users and damage the reputation of your application.

One way to handle transient errors gracefully and improve the user experience is to implement retry logic in your C# code.

In this guide, we'll explore what retry logic is, why it's important, and how to implement it effectively in your C# applications.

What is Retry Logic?

Retry logic is a technique used to automatically retry an operation that has failed, with the goal of eventually succeeding. When an error occurs, instead of giving up immediately, the application waits for a certain period of time, then tries again. This process continues until either the operation succeeds or a maximum number of retries is reached.

if at first you don't succeed try try again

 

Why is Retry Logic Important?

Implementing retry logic in your C# applications can help improve the user experience and increase the reliability and scalability of your application.

Here are some of the key benefits of using retry logic:

  1. Improved user experience: Retry logic can help your application handle errors gracefully, providing a better user experience for your customers. Instead of seeing an error message, they can simply wait a few seconds and try again.

  2. Increased reliability: Retry logic can help ensure that critical operations, such as database writes, are completed successfully. By retrying the operation if it fails, you can increase the overall reliability of your application.

  3. Scalability: Retry logic can help your application handle high loads and spikes in traffic. By retrying requests that failed due to temporary issues, you can reduce the number of failed requests and improve the overall scalability of your application.

How to Implement Retry Logic in C#

Retry Policy
Image Credit: c-sharpcorner.com

Now that you understand the benefits of retry logic, let's explore how to implement it in your C# applications. Here are the steps:

Step 1: Identify the Operation to Retry

The first step in implementing retry logic is to identify the operation that needs to be retried. This could be a database write, a network request, or any other operation that may fail due to temporary issues.

Step 2: Define the Retry Parameters

Once you've identified the operation to retry, you need to define the retry parameters. This includes the maximum number of retries, the delay between retries, and any additional parameters that are needed.

Step 3: Implement the Retry Logic

With the retry parameters defined, you can now implement the retry logic. This involves wrapping the operation in a try-catch block, and then retrying the operation if an exception is caught. Here are some example of retry pattern in C# to get you started:

Retry Logic with Waiting Time

            
    public static void WaitAndRetry(Action action, int maxRetries, TimeSpan retryInterval)
    {
        var retryCount = 0;
        while (retryCount < maxRetries)
        {
            try
            {
                action(); // Perform the operation here
                break;  // If the operation succeeds, exit the loop
            }
            catch (Exception ex)
            {
                // Log the exception here
                // If the maximum number of retries has been reached, rethrow the exception
                if (++retryCount >= maxRetries)
                {
                    throw new Exception($"Getting Exception : {ex.Message} after {retryCount} retries.", ex);
                }

                // Wait for a certain period of time before retrying
                Thread.Sleep(retryInterval);
            }
        }
    }
              

 

Retry Logic without Waiting Time


 
public static void Retry(Action action, int maxRetries)
{
    var retryCount = 0;
    while (true)
    {
        try
        {
            action();
            return;
        }
        catch when (retryCount < maxRetries)
        {
            retryCount++;
        }
        catch (Exception ex)
        {
            throw new Exception($"Getting Exception : {ex.Message} after {retryCount} retries.", ex);
        }
    }
}

 

Retry Logic with Waiting Time and Return Type

 
public static T WaitAndRetry<T>(Func<T> func, int maxRetries, TimeSpan retryInterval)
{
    var retryCount = 0;
    while (retryCount < maxRetries - 1)
    {
        try
        {
            return func();
        }
        catch (Exception ex)
        {
            // Log the exception here
            // If the maximum number of retries has been reached, rethrow the exception
            if (++retryCount >= maxRetries)
            {
                throw new Exception($"Getting Exception : {ex.Message} after {retryCount} retries.", ex);
            }

            // Wait for a certain period of time before retrying
            Thread.Sleep(retryInterval);
        }
    }
    return func();

}

 

Retry Logic without Waiting Time and Return Type


public static T Retry<T>(Func<T> func, int maxRetries)
{
var retryCount = 0;
while (true)
{
try
{
return func();
}
catch when (retryCount < maxRetries)
{
retryCount++;
}
catch (Exception ex)
{
throw new Exception($"Getting Exception : {ex.Message} after {retryCount} retries.", ex);
        }
    }
}

 

Asynchronous Retry Logic without Waiting Time

 
public static async Task RetryAsync(Func<Task> func, int maxRetries)
{
    for (var i = 0; i < maxRetries; i++)
    {
        try
        {
            await func();
            break;
        }
        catch (Exception ex)
        {
            throw new Exception($"Failed {i + 1}: Getting Exception : {ex.Message}");
}
}
}

 

Asynchronous Retry Logic with Waiting Time


public static async Task WaitAndRetryAsync(Func<Task> func, int maxRetries, TimeSpan retryInterval)
{
for (var i = 0; i < maxRetries; i++)
{
try
{
await func();
break;
}
catch when (i < maxRetries)
{
await Task.Delay(retryInterval);
}
catch (Exception ex)
{
throw new Exception($"Failed {i + 1}: Getting Exception : {ex.Message}");
}
}
}

 

Asynchronous Retry Logic without Waiting Time and Return Type


 
public static async Task<T> RetryAsync<T>(Func<Task<T>> func, int maxRetries)
{
    for (var i = 0; i < maxRetries; i++)
    {
        try
        {
            await func();
            break;
        }
        catch (Exception ex) when (i < maxRetries)
        {
            throw new Exception($"Failed {i + 1}: Getting Exception : {ex.Message}");
        }
    }
    return await func();
}

 

Asynchronous Retry Logic with Waiting Time and Return Type

 
public static async Task<T> WaitAndRetryAsync<T>(Func<Task<T>> func, int maxRetries, TimeSpan retryInterval)
{
    for (var i = 0; i < maxRetries; i++)
    {
        try
        {
            await func();
            break;
        }
        catch when (i < maxRetries)
        {
            await Task.Delay(retryInterval);
        }
        catch (Exception ex)
        {
            throw new Exception($"Failed {i + 1}: Getting Exception : {ex.Message}");
        }
    }
    return await func();
}

Step 4: Test and Refine the Retry Logic

Once you've implemented the retry logic, it's important to test it thoroughly and refine it as needed. Make sure to test the retry logic under different scenarios, such as high loads and network outages, to ensure that it works as expected. If you encounter any issues, refine the retry parameters or the implementation itself to improve its effectiveness.

        

public class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("App Started");

        await RetryAsync(3);

        Console.WriteLine("App Completed");
        Console.ReadKey();
    }

    public static async Task RetryAsync(int maxRetries)
    {

        for (var i = 0; i < maxRetries; i++)
        {
            try
            {
                await DoSomethingAsync();
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed {i + 1}: {ex.Message}");
            }
        }
    }
    public static async Task DoSomethingAsync()
    {
        //Processing something cool
        await Task.Delay(500);
        //Throwing Exception so that retry will work
        throw new Exception("Exception Occurred while Processing...");
    }

}

//Output:
/**
App Started
Failed 1: Exception Occurred while Processing...
Failed 2: Exception Occurred while Processing...
Failed 3: Exception Occurred while Processing...
App Completed
**/

Step 5: Monitor and Log Retries

Finally, it's important to monitor and log retries in your application. This can help you identify issues and refine the retry logic over time. You can use your application's logging framework to log retry attempts, along with any relevant information such as the operation that was retried, the number of retries, and the delay between retries.

Conclusion

Implementing retry logic in your C# applications can help you handle errors gracefully, improve the user experience, and increase the reliability and scalability of your application. By following the steps outlined in this guide, you can implement effective retry logic that will help your application recover from temporary issues and continue to provide value to your users.

Remember to test and refine your retry logic over time, and to monitor retries to identify any issues that may arise. With these best practices in place, you can build resilient and scalable applications that meet the needs of your users.

,