How to write retry logic in C#?

Retry logic is implemented to handle transient failures that may resolve themselves after a brief delay. This pattern is essential when working with network operations, database connections, or external APIs that may temporarily fail due to network issues, service overload, or temporary unavailability.

It's important to log all connectivity failures that cause a retry so that underlying problems with the application, services, or resources can be identified. Implement retry logic only where you have the full context of a failing operation and when the operation is idempotent (safe to repeat).

Syntax

Following is the basic syntax for implementing retry logic −

public static void Retry(int maxAttempts, TimeSpan delay, Action operation) {
    var attempts = 0;
    do {
        try {
            attempts++;
            operation();
            break; // Success
        }
        catch (Exception ex) {
            if (attempts == maxAttempts)
                throw; // Re-throw after max attempts
            Task.Delay(delay).Wait();
        }
    } while (true);
}

Using Basic Retry Logic

The following example demonstrates a simple retry helper that attempts an HTTP request multiple times with exponential backoff −

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program {
    public static void Main() {
        HttpClient client = new HttpClient();
        HttpResponseMessage response = null;
        var retryAttempts = 3;
        var delay = TimeSpan.FromSeconds(2);

        try {
            RetryHelper.Retry(retryAttempts, delay, () => {
                response = client.GetAsync("https://httpstat.us/500").Result;
                if (!response.IsSuccessStatusCode) {
                    throw new HttpRequestException($"Request failed with status: {response.StatusCode}");
                }
            });
            Console.WriteLine("Request succeeded!");
        }
        catch (Exception ex) {
            Console.WriteLine($"All retry attempts failed: {ex.Message}");
        }
        finally {
            client.Dispose();
        }
    }
}

public static class RetryHelper {
    public static void Retry(int maxAttempts, TimeSpan delay, Action operation) {
        var attempts = 0;
        do {
            try {
                attempts++;
                Console.WriteLine($"Attempt {attempts}");
                operation();
                break;
            }
            catch (Exception ex) {
                Console.WriteLine($"Attempt {attempts} failed: {ex.Message}");
                if (attempts == maxAttempts) {
                    Console.WriteLine("Max attempts reached. Giving up.");
                    throw;
                }
                Task.Delay(delay).Wait();
            }
        } while (true);
    }
}

The output of the above code is −

Attempt 1
Attempt 1 failed: Request failed with status: InternalServerError
Attempt 2
Attempt 2 failed: Request failed with status: InternalServerError
Attempt 3
Attempt 3 failed: Request failed with status: InternalServerError
Max attempts reached. Giving up.
All retry attempts failed: Request failed with status: InternalServerError

Using Exponential Backoff

Exponential backoff increases the delay between retry attempts to reduce load on the failing service −

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program {
    public static void Main() {
        HttpClient client = new HttpClient();
        
        try {
            RetryHelper.RetryWithExponentialBackoff(3, TimeSpan.FromSeconds(1), () => {
                var response = client.GetAsync("https://httpstat.us/503").Result;
                if (!response.IsSuccessStatusCode) {
                    throw new HttpRequestException($"Service unavailable: {response.StatusCode}");
                }
                Console.WriteLine("Request succeeded!");
            });
        }
        catch (Exception ex) {
            Console.WriteLine($"Final failure: {ex.Message}");
        }
        finally {
            client.Dispose();
        }
    }
}

public static class RetryHelper {
    public static void RetryWithExponentialBackoff(int maxAttempts, TimeSpan baseDelay, Action operation) {
        var attempts = 0;
        do {
            try {
                attempts++;
                Console.WriteLine($"Attempt {attempts}");
                operation();
                break;
            }
            catch (Exception ex) {
                Console.WriteLine($"Attempt {attempts} failed: {ex.Message}");
                if (attempts == maxAttempts) {
                    throw;
                }
                
                var delay = TimeSpan.FromMilliseconds(baseDelay.TotalMilliseconds * Math.Pow(2, attempts - 1));
                Console.WriteLine($"Waiting {delay.TotalSeconds} seconds before retry...");
                Task.Delay(delay).Wait();
            }
        } while (true);
    }
}

The output of the above code is −

Attempt 1
Attempt 1 failed: Service unavailable: ServiceUnavailable
Waiting 1 seconds before retry...
Attempt 2
Attempt 2 failed: Service unavailable: ServiceUnavailable
Waiting 2 seconds before retry...
Attempt 3
Attempt 3 failed: Service unavailable: ServiceUnavailable
Final failure: Service unavailable: ServiceUnavailable

Retry Strategy Comparison Fixed Delay Attempt 1: 0s Attempt 2: 2s delay Attempt 3: 2s delay Consistent intervals Exponential Backoff Attempt 1: 0s Attempt 2: 1s delay Attempt 3: 2s delay Increasing intervals Exponential backoff reduces load on failing services

Key Rules

  • Use for transient failures only − Network timeouts, temporary service unavailability, rate limiting.

  • Ensure operations are idempotent − Safe to repeat without causing side effects.

  • Set maximum retry limits − Prevent infinite retry loops that could cause resource exhaustion.

  • Log retry attempts − Monitor patterns to identify underlying infrastructure issues.

  • Use exponential backoff − Reduces load on failing services and increases success probability.

Common Use Cases

  • HTTP API calls − Handling temporary network issues or service overload.

  • Database connections − Dealing with connection pool exhaustion or temporary database unavailability.

  • File operations − Handling file locks or temporary I/O issues.

  • Message queue operations − Ensuring message delivery despite temporary broker issues.

Conclusion

Retry logic is essential for building resilient applications that can handle transient failures gracefully. Use fixed delays for simple scenarios and exponential backoff for high-load environments. Always ensure operations are idempotent and implement proper logging to monitor retry patterns and identify underlying issues.

Updated on: 2026-03-17T07:04:36+05:30

1K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements