Previous Model-Binding-NET-Core Model Binding and Validation in ASP.NET Core Next

Synchronous and Asynchronous in .NET Core, C#

Synchronous vs Asynchronous in .NET Core C#

Core Idea

Synchronous: Runs on the calling thread and blocks it until the work finishes.
Asynchronous: Returns quickly with a Task or Task<T>, freeing the calling thread while work completes.

Examples

Synchronous File Read

string ReadFileSync(string path)
{
    return File.ReadAllText(path); // blocks calling thread
}
    

Asynchronous File Read

async Task<string> ReadFileAsync(string path)
{
    return await File.ReadAllTextAsync(path);
}
    

ASP.NET Core Controller

// synchronous
public IActionResult GetSync()
{
    var json = File.ReadAllText("data.json");
    return Content(json, "application/json");
}

// asynchronous
public async Task<IActionResult> GetAsync()
{
    var json = await File.ReadAllTextAsync("data.json");
    return Content(json, "application/json");
}
    

Database Example

// synchronous
var items = dbContext.Items.ToList();

// asynchronous
var items = await dbContext.Items.ToListAsync();
    

Streaming Example

async IAsyncEnumerable<int> StreamNumbersAsync()
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}
    

Why Prefer Async?

  • Scalability: Async frees threads while waiting for I/O.
  • Responsiveness: Avoids UI freezing in desktop/mobile apps.
  • Resource efficiency: Uses fewer threads, less memory pressure.
  • Better latency: Handles high load more efficiently.

Best Practices

  • Avoid async void except for event handlers.
  • Use "async all the way" — avoid .Result and .Wait().
  • Do not wrap sync code in Task.Run in ASP.NET Core.
  • Use ConfigureAwait(false) in libraries.
  • Propagate CancellationToken.
  • Handle exceptions with try/catch around await.
  • Prefer Task / Task<T>, not void.
  • Use IAsyncEnumerable<T> for streaming data.

Deadlocks

// Bad: can deadlock in UI apps
var result = SomeAsync().Result;
    

In UI apps, this blocks the thread and prevents async continuations from running. ASP.NET Core avoids most of these deadlocks, but blocking is still discouraged.

Checklist

  • Is it I/O-bound? → Use async.
  • Runs in server/GUI? → Use async for scalability/responsiveness.
  • Propagate CancellationToken.
  • Return Task, not void.
  • Avoid blocking (.Result, .Wait()).
  • Use ConfigureAwait(false) in libraries.

Example with Cancellation & Exception Handling

public async Task<string> DownloadStringWithCancellationAsync(
    HttpClient httpClient, string url, CancellationToken ct)
{
    try
    {
        using var resp = await httpClient.GetAsync(
            url, HttpCompletionOption.ResponseHeadersRead, ct);

        resp.EnsureSuccessStatusCode();
        return await resp.Content.ReadAsStringAsync(ct);
    }
    catch (OperationCanceledException) when (ct.IsCancellationRequested)
    {
        throw;
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException("Download failed", ex);
    }
}
    

Summary

Synchronous: blocks threads. Good only for trivial CPU-bound work.
Asynchronous: frees threads during I/O. Better scalability, responsiveness, and resource usage.

Quick comparison

Aspect Synchronous Asynchronous
Execution model Call blocks until work completes Call returns a Task; awaiting yields while work continues
Thread usage Occupies a thread the entire time Frees the thread during waits; resumes later on a pooled thread
Best for Short, CPU-bound, in-process work I/O-bound (DB, HTTP, file, message bus) and bursty load
Throughput Lower under concurrency; risk of thread pool starvation Higher under concurrency; better scalability
Latency for a single call Same or marginally lower (less machinery) Same or marginally higher (state machine)
UI responsiveness Risk of freezes Stays responsive
Error propagation Immediate exceptions Faulted Tasks; exceptions surface on await
Coding model Straight-line, simple async/await state machine; “async all the way”

Sources: The .NET async/await model frees threads during I/O waits and resumes work when results are ready. This improves scalability, not raw speed.

Mental model that actually clicks

Synchronous call: “Wait here.” Your thread sits idle while the DB/HTTP/file system works. Under load, many threads sit idle, and new requests queue.

Asynchronous call: “Start it, tell me when.” You start the I/O, return the thread to the pool, and resume later when the result arrives. This keeps a small thread pool serving many requests.

Direct answer: Prefer async in ASP.NET Core because it scales better under concurrent I/O. It doesn’t make the DB faster; it stops your server from wasting threads waiting.

When Not to Use Async

  • Very short CPU-bound operations.
  • Low-level library code where overhead matters.
  • Optimized synchronous pipelines not waiting on I/O.

When to use async vs sync

Use async (recommended)

  • I/O-bound work: DB calls (EF Core), HTTP (HttpClient), file/network streams, cache/messaging (Redis, S3, Kafka).
  • Server endpoints: Controllers/minimal APIs to keep the entire pipeline non-blocking.
  • Libraries called by async callers: Preserve responsiveness and avoid sync-over-async deadlocks in non-ASP contexts.

Use sync (valid cases)

  • Short, CPU-bound operations: Small calculations, trivial mapping, in-memory lookups.
  • No async API available: Keep it sync; don’t wrap with Task.Run on the server just to “look async.”
  • CPU-bound and long-running: For heavy CPU work, consider background jobs (Hangfire/Queues) or Task.Run only when deliberately offloading CPU.

Practical examples

Controller I/O call

// Prefer async: frees thread while DB/HTTP runs
[HttpGet("{id:int}")]
public async Task<ActionResult<ProductDto>> Get(int id, CancellationToken ct)
{
    var entity = await _repo.GetByIdAsync(id, ct);
    if (entity is null) return NotFound();
    return Ok(_mapper.Map<ProductDto>(entity));
}

// Synchronous: blocks thread during I/O (avoid in ASP.NET Core)
[HttpGet("{id:int}")]
public ActionResult<ProductDto> GetSync(int id)
{
    var entity = _repo.GetById(id); // Blocks thread
    if (entity is null) return NotFound();
    return Ok(_mapper.Map<ProductDto>(entity));
}
    

Fan-out HTTP calls in parallel

public async Task<WeatherSummary> GetSummaryAsync(CancellationToken ct)
{
    var a = _http.GetStringAsync("svc/A", ct);
    var b = _http.GetStringAsync("svc/B", ct);
    await Task.WhenAll(a, b); // both awaited concurrently
    return Combine(await a, await b);
}
    

Streaming file I/O

await using var input = File.OpenRead(path);
await using var output = File.Create(destPath);
await input.CopyToAsync(output, 81920, ct);
    

CPU-bound offload (be deliberate)

// Only when you truly want to move CPU work off the request thread
var result = await Task.Run(() => DoCpuIntensiveWork(model), ct);
    

Common pitfalls and how to avoid them

  • Blocking on async: Avoid .Result, .Wait(), .GetAwaiter().GetResult(). Fix: make the whole call chain async.
  • Mixing sync and async I/O in ASP.NET Core: Avoid synchronous I/O (Stream.Read). Fix: use ReadAsync, WriteAsync, ReadFormAsync.
  • Using Task.Run to “fake” async for I/O: Still burns a thread. Fix: use real async APIs.
  • Forgetting cancellation and timeouts: Always thread through CancellationToken, set HttpClient/DB timeouts.
  • Leaking HttpClient sockets: Don’t create new HttpClient per request. Fix: use IHttpClientFactory.
  • ConfigureAwait in ASP.NET Core: Usually unnecessary in app code, fine in libraries.
  • Ignoring async exceptions: Always await tasks or explicitly observe/log them.

Why async improves scalability in ASP.NET Core

  • Thread pool economics: Sync I/O pins threads, async I/O frees them.
  • Latency vs throughput: Single-request speed is similar; async scales better under concurrency.

Minimal, opinionated guidance

  • Do: Make controllers and data access async; pass CancellationToken; use Task.WhenAll; use IHttpClientFactory; go “async all the way.”
  • Don’t: Block on async; wrap sync I/O in Task.Run; mix sync/async I/O; create HttpClient per call.
  • Use sync: Only for small, CPU-bound, local work; otherwise prefer async.
Back to Index
Previous Model-Binding-NET-Core Model Binding and Validation in ASP.NET Core Next
*