Async pitfalls
The classic async mistakes — blocking, async void, fire-and-forget — and how to avoid them.
10 min read
You will learn
- Explain why blocking on async code is harmful
- Know why async void is dangerous
- Avoid unobserved fire-and-forget tasks
- Know when ConfigureAwait(false) and ValueTask matter
Easy to write, easy to get subtly wrong
async/await makes asynchronous code look simple, but a few mistakes come up again and again in real codebases. Recognizing them will save you painful production debugging.
1. Blocking on async code ("sync over async")
// ❌ Blocks the current thread until the download finishes
string html = http.GetStringAsync(url).Result;
http.GetStringAsync(url).Wait();
string html2 = http.GetStringAsync(url).GetAwaiter().GetResult();The whole point of async is to free threads while waiting. Blocking throws that away:
- In ASP.NET Core, each blocked request pins a thread-pool thread. Under load the pool runs dry, new requests queue, and the server appears frozen — thread-pool starvation.
- In UI apps (and old ASP.NET), blocking on the UI thread while the awaited code tries to resume on that same thread causes a deadlock.
✅ Make the caller async and await. "Async all the way."
2. async void
// ❌ Nobody can await this, and an exception here crashes the process
async void SendWelcomeEmail(User user) => await email.SendAsync(user.Email, "Welcome!");
// ✅ Return Task
async Task SendWelcomeEmailAsync(User user) => await email.SendAsync(user.Email, "Welcome!");async void exists only for event handlers (button.Click += async (s, e) => ...), because their signature must return void. Everywhere else, return Task.
3. Forgetting to await (fire-and-forget)
public async Task<IActionResult> PlaceOrder(Order order)
{
await db.SaveAsync(order);
analytics.TrackAsync(order); // ❌ not awaited: exceptions vanish, may be cut off
return Ok();
}The compiler warns (CS4014) when you call an async method without awaiting it — don't ignore that warning. If work genuinely should continue after the response, hand it to a background service or a queue (Track 7 and 8) rather than an orphaned task.
4. Using Task.Run for I/O
// ❌ Wastes a pool thread that just waits for the database
var users = await Task.Run(() => db.Users.ToList());
// ✅ Use the real async API
var users = await db.Users.ToListAsync();Task.Run is for CPU-bound work. I/O already has async APIs that use no thread while waiting.
5. Shared state across awaits
Code after an await may run on a different thread, and other requests may run in between. Don't assume nothing changed — especially with shared, mutable static state. Thread safety is the topic of the last lesson in this track.
ConfigureAwait(false)
By default, await resumes on the captured context (the UI thread in desktop apps). ConfigureAwait(false) says "resume anywhere":
var json = await http.GetStringAsync(url).ConfigureAwait(false);- In library code that might be used from UI apps: common practice, avoids deadlocks and context hops.
- In ASP.NET Core app code: there's no synchronization context, so it makes no difference — you can omit it.
ValueTask
ValueTask<T> avoids allocating a Task when a method usually completes synchronously (e.g. a cache hit). It's an optimization for hot paths with rules (await it only once). Default to Task<T>.
Analyzers help
Many of these mistakes are caught by compiler warnings and analyzers (CS4014, and rules like "Do not use .Result" in popular analyzer packages). Treat async warnings as errors in your projects.
Key takeaways
- Never block on async code with
.Result/.Wait()— make the caller async. - Avoid
async voidexcept for event handlers; returnTask. - Always await (or deliberately hand off) the tasks you start.
Task.Runis for CPU work;ConfigureAwait(false)matters mainly in libraries.
Quiz
4 questions · 3 correct to pass1.Why is calling `.Result` on an unfinished task in an ASP.NET Core endpoint a bad idea?
2.What's the problem with `async void` methods?
3.You call `SaveAsync();` without awaiting it. What can go wrong?
4.Where is ConfigureAwait(false) most relevant?
To complete this lesson
- ○ Pass the quiz (3/4 correct)