| JIT vs AOT in .NET Core :👈 | 👉:C# Serial Communication |
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 voidexcept for event handlers. - Use "async all the way" — avoid
.Resultand.Wait(). - Do not wrap sync code in
Task.Runin ASP.NET Core. - Use
ConfigureAwait(false)in libraries. - Propagate
CancellationToken. - Handle exceptions with
try/catcharoundawait. - Prefer
Task/Task<T>, notvoid. - 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, notvoid. - 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.
| JIT vs AOT in .NET Core :👈 | 👉:C# Serial Communication |