C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
210: Handling Retries with Polly
In a perfect world, every API call you make returns a 200 OK in under 100 milliseconds. But we don't live in that world. We live in a world of "transient faults"—those annoying, brief hiccups where a service is rebooting, a network switch is sneezing, or a database is momentarily overloaded. If you just let your application crash the first time a request fails, your users are going to hate you.
The manual loop that works until it doesn't
When I first started writing C#, my instinct was to just wrap the failing call in a for loop. It seems logical: try it three times, and if it still fails, then give up. It looks something like this:
public async Task<PaymentResponse> ProcessPayment(PaymentRequest request)
{
int retryCount = 3;
for (int i = 0; i < retryCount; i++)
{
try
{
return await _paymentGateway.ChargeAsync(request);
}
catch (HttpRequestException) when (i < retryCount - 1)
{
// Wait a bit before trying again
await Task.Delay(1000);
}
}
throw new Exception("Payment gateway is down after multiple attempts.");
}
On the surface, this solves the immediate problem. But here's where it falls apart in a production environment. First, it's rigid. If you want to change the retry logic for ten different services, you're copying and pasting this loop everywhere. Second, it uses a "fixed interval." If the payment gateway is struggling under a massive load, and 1,000 of your application instances all retry every exactly one second, you've just accidentally launched a Distributed Denial of Service (DDoS) attack against your own provider. I call this the "thundering herd" problem, and it's a great way to get your API key revoked.
Offloading the chaos to Polly
This is why we use Polly. It's the industry-standard resilience library for .NET. Instead of writing loops, you define a policy—a set of rules for how the system should behave when things go wrong—and then you wrap your call inside that policy. In the modern version of Polly (v8+), we use the ResiliencePipeline.
using Polly;
using Polly.Retry;
// Define the pipeline
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1)
})
.Build();
// Use the pipeline to execute your call
var response = await pipeline.ExecuteAsync(async token =>
await _paymentGateway.ChargeAsync(request), cancellationToken);
Notice the shift in responsibility. The ProcessPayment method no longer cares how the retry happens; it just knows that it's executing within a resilience pipeline. We've separated the "what" (charging a card) from the "how" (retrying when the network flickers).
Why waiting exactly one second is a bad idea
You'll notice I used DelayBackoffType.Exponential and UseJitter = true in the Polly example. This is the "secret sauce" of professional software engineering. Exponential backoff means the wait time grows (1s, 2s, 4s...), giving the failing service more breathing room to recover.
Jitter adds a random amount of time to that delay. Instead of everyone retrying at exactly 2.0 seconds, some will retry at 1.8s, others at 2.2s. This spreads the load across the timeline, preventing those spikes of traffic that crash recovered servers. It's a small configuration change, but it's the difference between a system that recovers gracefully and one that enters a death spiral.
Now, a word of caution: retries are only for transient faults. If the API returns a 401 Unauthorized or a 400 Bad Request, retrying a thousand times won't help; the request is fundamentally broken. Always make sure your ShouldHandle predicate is targeting the right exceptions or status codes. Don't retry a password failure; you'll just get your user's account locked.
📋 Practical Task
Exercise: Implementing a Resilient Weather API Client
You are building a weather dashboard that consumes a third-party API. The API is notoriously flaky and frequently throws HttpRequestException during peak hours. Your goal is to implement a resilient client using Polly.
Requirements:
- Create a
WeatherServiceclass with a methodGetTemperatureAsync(string city). - Configure a Polly
ResiliencePipelinethat:- Handles only
HttpRequestException. - Retries up to 5 times.
- Uses Exponential Backoff.
- Enables Jitter to avoid the thundering herd problem.
- Starts with an initial delay of 2 seconds.
- Handles only
- Wrap the API call (you can simulate the API call with a method that randomly throws an exception) within this pipeline.
Success Criteria: Your code should demonstrate that the application doesn't crash on the first failure and that the time between retries increases progressively rather than remaining constant.
There are no comments for now.