Skip to main content

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 CharacteristicEstimated DurationUser waits for result?Recommended Strategy
Data Reading (Query)< 1sYesasync/await (Controller + EF)
Simple Save< 2sYesasync/await (Controller + EF)
Complex Calculation / Report> 10sYesSQL Optimization (Stored Proc) or Cache
Mass Process (e.g., Import)> 30sNoBackground Job + UI Polling
Critical Multi-Table ProcessVariableYes (Business Req)SQL Optimization + async/await (ACID)
Side Effects (Email/Log)< 2sNoBackground 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 await a 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 void methods 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) or Task.Run (Safe only if exception handling is robust).
  • Use Case: Bulk Imports, Mass Price Updates, PDF Generation.
  • Flow:
    1. User sends command -> Server responds 202 Accepted.
    2. A Worker (e.g., Hangfire) executes the complex logic.
    3. User sees an "In Progress" status on the UI.

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

  1. Introduce async/await wherever possible to scale the server, respecting Legacy MVC rules.
  2. Keep complex transactions Solid (ACID) if the business requires immediate confirmation.
  3. Use Background Jobs for everything the user accepts to "receive later".