The Problem: 1000 users hit GET /api/restaurants. Your code does _db.Restaurants.ToList(). Each call blocks a thread for 200ms. Server has 200 threads. 1000 users = 800 users waiting. Site freezes. 503 Service Unavailable.
1. The Zomato Kitchen Analogy
Sync = 1 Cook, 1 Order: Cook takes order โ waits at oven 10 mins โ can't take new orders. 5 orders = 50 min wait.
Async = 1 Cook, 10 Orders: Cook puts pizza in oven โ sets timer โ takes next order. Oven works while cook works. 5 orders = 12 mins. Cook never idle.
Thread = Cook. DB call = Oven. Async = Don't block cook while waiting for oven.
2. Fix It: async/await + ToListAsync
Bad - Blocks Thread:
[HttpGet]
public IActionResult Get() {
var data = _db.Restaurants.ToList(); // Thread BLOCKED 200ms waiting for DB
return Ok(data);
}
Good - Releases Thread:
[HttpGet]
public async Task Get() {
var data = await _db.Restaurants.ToListAsync(); // Thread RELEASED. Handles other requests.
return Ok(data); // Thread comes back when DB done
}
Result: 200 threads can now handle 10,000 requests. Because threads aren't blocked waiting.
3. CancellationToken - Stop Wasted Work
Problem: User searches restaurants โ Waits 2 sec โ Closes browser. Your API keeps hitting DB for 10 more sec. Wasted CPU, DB connections.
Fix: Accept CancellationToken. ASP.NET auto-cancels when client disconnects.
[HttpGet]
public async Task Search(string q, CancellationToken ct) {
// Pass token to every async call
var data = await _db.Restaurants
.Where(r => r.Name.Contains(q))
.ToListAsync(ct); // Throws OperationCanceledException if user cancels
return Ok(data);
}
Test: Start request in Postman โ Cancel โ Check logs. See OperationCanceledException. DB query stopped. Server saved.
3 Rules of Async:
1. async all the way: Controller โ Service โ Repo โ DB. If 1 method is sync, you lose benefit.
2. Never .Result or .Wait(): Causes deadlocks. Always await.
3. Always pass CancellationToken: From controller down to DB. HttpClient, EF Core, all support it.
Career-Killer Mistake: async void in controllers or services.
public async void ProcessOrder() { await _db.SaveChangesAsync(); }
Result: Exceptions crash entire app. Can't await. Can't catch. Fire-and-forget = fire-and-crash.
Fix: Always async Task. Only async void for event handlers like button clicks.
1. Theory: Thread Pool Starvation
ASP.NET has ~200-1000 threads by default. Sync DB call = thread sleeps.
Math: 200 threads ร 200ms per request = 1000 req/sec max.
With async: Thread released during 200ms wait. Same 200 threads = 20,000 req/sec. 20x throughput.
How to detect: App slow, CPU low, ThreadPool PendingWorkItemCount high. Means threads blocked, not working.
2. ConfigureAwait(false) - Do You Need It?
Old advice: Use ConfigureAwait(false) in libraries.
await _db.Restaurants.ToListAsync().ConfigureAwait(false);
ASP.NET Core Reality: No SynchronizationContext. ConfigureAwait(false) does nothing. Safe to omit.
Rule: In ASP.NET Core APIs, skip it. In libraries used by WPF/WinForms, keep it. Zomato API code = no ConfigureAwait.
3. Parallel Async - When You Need Multiple DB Calls
Bad - Sequential: 200ms + 200ms = 400ms
var restaurants = await _db.Restaurants.ToListAsync();
var offers = await _db.Offers.ToListAsync();
Good - Parallel: max(200ms, 200ms) = 200ms
var restTask = _db.Restaurants.ToListAsync(ct);
var offersTask = _db.Offers.ToListAsync(ct);
await Task.WhenAll(restTask, offersTask);
var restaurants = restTask.Result;
var offers = offersTask.Result;
Warning: EF Core DbContext not thread-safe. Use separate context per query or AsNoTracking() + new context.
4. CancellationToken in HttpClient
Calling 3rd party APIs? Always pass token.
[HttpGet("weather")]
public async Task GetWeather(CancellationToken ct) {
using var http = new HttpClient();
// If user cancels, HttpClient cancels request. Saves bandwidth.
var json = await http.GetStringAsync("https://api.weather.com/data", ct);
return Ok(json);
}
Without token: User closes page, but your server still downloads 5MB JSON. Wasted.
5. Timeout + Cancellation Link
Combine user cancel + server timeout:
public async Task Get(CancellationToken userCt) {
// Cancel if user disconnects OR 5 seconds pass
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(userCt, timeoutCts.Token);
var data = await _db.Restaurants.ToListAsync(linkedCts.Token);
return Ok(data);
}
Result: Slow DB query auto-cancelled after 5s. Returns 499 Client Closed Request or 504 Gateway Timeout.
Career-Killer Mistake #2: Using Task.Run in ASP.NET to "make it async".
return Task.Run(() => _db.Restaurants.ToList());
Why fired: You just moved blocking work to ThreadPool thread. Still blocks 1 thread. Zero scalability gain. Plus overhead.
Fix: Use true async: ToListAsync(). Task.Run is for CPU-bound work, not I/O.
Next: Your API is fast + cancellable.
Next: Entity Framework Core - Stop writing
SqlConnection. Use LINQ:
_db.Restaurants.Where(r => r.Rating > 4). This is how you actually talk to DB like a pro. Final core piece.
No comments yet. Be the first to share your thoughts!