🔄 Retry Patterns = Resilient Apps
Transient failures are common. Retry patterns with async/await make your applications more resilient.
📝 Retry Implementation
// Basic Retry public async TaskRetryAsync (Func > operation, int maxRetries = 3) { int attempt = 0; while (attempt < maxRetries) { try { return await operation(); } catch (Exception) when (attempt < maxRetries - 1) { attempt++; await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); } } throw new Exception("Operation failed after retries"); } // Exponential Backoff public class RetryOptions { public int MaxRetries { get; set; } = 3; public int InitialDelayMs { get; set; } = 1000; public int MaxDelayMs { get; set; } = 30000; public double BackoffMultiplier { get; set; } = 2.0; } public async Task RetryWithBackoffAsync ( Func > operation, RetryOptions options = null) { options ??= new RetryOptions(); int attempt = 0; int delay = options.InitialDelayMs; while (attempt < options.MaxRetries) { try { return await operation(); } catch (Exception) when (attempt < options.MaxRetries - 1) { attempt++; await Task.Delay(delay); delay = Math.Min( (int)(delay * options.BackoffMultiplier), options.MaxDelayMs ); } } throw new Exception("Operation failed after retries"); }
🎯 Advanced Patterns
// Retry with Jitter public async TaskRetryWithJitterAsync ( Func > operation, RetryOptions options = null) { options ??= new RetryOptions(); int attempt = 0; var random = new Random(); while (attempt < options.MaxRetries) { try { return await operation(); } catch (Exception) when (attempt < options.MaxRetries - 1) { attempt++; var jitter = random.Next(0, 1000); var delay = Math.Min( options.InitialDelayMs * (int)Math.Pow(options.BackoffMultiplier, attempt), options.MaxDelayMs ) + jitter; await Task.Delay(delay); } } throw new Exception("Operation failed after retries"); } // Retry with Circuit Breaker public class CircuitBreakerRetry { private int _failureCount = 0; private DateTime _lastFailure; private bool _isOpen = false; public async Task ExecuteAsync (Func > operation) { if (_isOpen && DateTime.UtcNow - _lastFailure < TimeSpan.FromSeconds(30)) { throw new CircuitBreakerOpenException(); } try { var result = await operation(); _failureCount = 0; _isOpen = false; return result; } catch (Exception ex) { _failureCount++; _lastFailure = DateTime.UtcNow; if (_failureCount >= 3) { _isOpen = true; } throw; } } } // Retry with Exponential Backoff and Jitter public class ResilientClient { public async Task SendAsync (Func > request) { int retryCount = 0; int maxRetries = 3; int baseDelay = 1000; while (retryCount <= maxRetries) { try { return await request(); } catch (Exception ex) when (retryCount < maxRetries && IsTransient(ex)) { retryCount++; int delay = (int)(baseDelay * Math.Pow(2, retryCount)); int jitter = Random.Shared.Next(0, 1000); await Task.Delay(delay + jitter); } } throw new Exception("Request failed after retries"); } private bool IsTransient(Exception ex) { return ex is TimeoutException || ex is HttpRequestException || ex is IOException; } }
💡 Retry Pattern Tips
- Use exponential backoff
- Add jitter for distribution
- Circuit breaker for repeated failures
- Log retry attempts
- Set appropriate max retries
Retry patterns make your applications more resilient to transient failures. They're essential for production systems.
