Async Strategy: Scalability & Background Processing
1. Core Principles
There is often confusion between the technical adoption of async/await and the architectural shift towards asynchronous processes (Background Jobs).
- Technical Async (
async/await): Scalability. Frees up IIS threads while waiting for I/O (Database, API), allowing the server to handle more concurrent requests. The user still waits for the response. - Architectural Async (Background Jobs): Responsiveness. Offloads heavy work to a separate process. The user gets an immediate response ("Accepted"), and the work happens later.
2. Decision Framework
Use this matrix to decide the strategy for any given operation.
| Operation Characteristic | Estimated Duration | User waits for result? | Recommended Strategy |
|---|---|---|---|
| Data Reading (Query) | < 1s | Yes | async/await (Controller + EF) |
| Simple Save | < 2s | Yes | async/await (Controller + EF) |
| Complex Calculation / Report | > 10s | Yes | SQL Optimization (Stored Proc) or Cache |
| Mass Process (e.g., Import) | > 30s | No | Background Job + UI Polling |
| Critical Multi-Table Process | Variable | Yes (Business Req) | SQL Optimization + async/await (ACID) |
| Side Effects (Email/Log) | < 2s | No | Background Job (Fire & Forget) |
3. Implementation A: The "Async/Await" Standard
Goal: Maximize server throughput for short I/O operations.
The Doubt: "Can I always do it?"
Yes. Even a complex transaction touching 10 tables can be managed with await Context.SaveChangesAsync().
- Advantage: The server does not block while waiting for the DB.
- Limitation: The user still waits for the operation to complete.
⚠️ Technical Rules (Legacy MVC)
[!WARNING] ASP.NET Legacy (4.x) behaves differently from .NET Core. Ignoring these rules will cause Deadlocks and NullReferenceExceptions.
Rule 1: Prevent Deadlocks (ConfigureAwait(false))
In Legacy MVC, the SynchronizationContext is tied to the Request Thread.
- The Problem: If you
awaita Task without configuring it, it tries to resume on the original Request Thread. If that thread is blocked, you deadlock. - The Fix: Libraries and Services MUST use
.ConfigureAwait(false).// BAD: Will try to capture SyncContext, risking deadlock
await _repository.GetDataAsync();
// GOOD: Runs the rest of the method on a thread pool thread
await _repository.GetDataAsync().ConfigureAwait(false);
Rule 2: Beware of Context Loss (HttpContext.Current)
When you use ConfigureAwait(false), the continuation might run on a random thread where HttpContext.Current is NULL.
- The Fix: Capture needed data before the async call.
public async Task<ActionResult> DoWork() {
var username = User.Identity.Name; // Capture HERE
await _service.ProcessAsync(username).ConfigureAwait(false);
// Do NOT access User.Identity here.
return View();
}
Rule 3: NO async void
- The Problem:
async voidmethods cannot be tracked. If they fail, they crash the entire IIS Process (w3wp.exe). - The Fix: Always return
async Task.
Rule 4: Handle Cancellation
Long-running async IO can hang. Pass CancellationToken where possible.
4. Implementation B: Background Processing
Goal: Handle "heavy" operations without blocking the user.
1. Fire-and-Forget (Use with caution)
- Use Case: Logs, Statistics, Non-critical Emails.
- Mechanism:
HostingEnvironment.QueueBackgroundWorkItem(Legacy) orTask.Run(Safe only if exception handling is robust).
2. Job Queue Patterns (Recommended)
- Use Case: Bulk Imports, Mass Price Updates, PDF Generation.
- Flow:
- User sends command -> Server responds
202 Accepted. - A Worker (e.g., Hangfire) executes the complex logic.
- User sees an "In Progress" status on the UI.
- User sends command -> Server responds
5. Advanced Scenarios: Transactions
When we need to touch many tables (e.g., "Approve Order").
Option A: ACID (Synchronous/Async Transaction)
- Approach:
using (var tran = db.Database.BeginTransaction()) { ... } - Pros: Guaranteed consistency.
- Cons: DB blocking.
- Verdict: Keep for "Core" operations (e.g., Checkout). Optimize SQL, do not break the transaction.
Option B: Eventual Consistency (Saga)
- Approach: Break operation into phases (Status Pending -> Background Step 1 -> Background Step 2).
- Pros: Fast UI, Resilience.
- Cons: Complexity (Manual Rollback).
- Verdict: Use for "Batch" processes or non-real-time interactions.
6. Conclusion
- Introduce
async/awaitwherever possible to scale the server, respecting Legacy MVC rules. - Keep complex transactions Solid (ACID) if the business requires immediate confirmation.
- Use Background Jobs for everything the user accepts to "receive later".