asynchronous-programming
async-await
programming-confusion
duplicate-issue
software-development

Async Methods Confusion

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Async/await syntax in C# and JavaScript simplifies asynchronous programming but introduces common confusions: blocking on async code (.Result/.Wait() causing deadlocks), forgetting to await an async call (fire-and-forget), misunderstanding what async actually does, and confusion about when code runs on which thread. The key insight is that async does not make code run on a background thread — it allows the current thread to be released during await and resumed later, enabling non-blocking I/O operations.

What async/await Actually Does

csharp
1// async does NOT create a new thread
2// It enables the method to "yield" control during await
3
4public async Task<string> FetchDataAsync()
5{
6    Console.WriteLine($"Before await: Thread {Thread.CurrentThread.ManagedThreadId}");
7
8    // This releases the current thread during the HTTP call
9    string result = await httpClient.GetStringAsync("https://api.example.com/data");
10
11    Console.WriteLine($"After await: Thread {Thread.CurrentThread.ManagedThreadId}");
12    // May be the same or different thread depending on SynchronizationContext
13
14    return result;
15}
16
17// Equivalent desugared version (simplified):
18public Task<string> FetchDataAsync_Desugared()
19{
20    var stateMachine = new FetchDataStateMachine();
21    stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();
22    stateMachine.MoveNext();
23    return stateMachine.builder.Task;
24}

The compiler transforms async methods into state machines. When await is reached, the method returns a Task and the calling code continues. When the awaited operation completes, the state machine resumes from where it left off.

Confusion 1: Forgetting to Await

csharp
1// WRONG: Missing await — method runs but result is ignored
2public async Task ProcessAsync()
3{
4    SaveToDatabase();  // this is synchronous — fine
5    FetchDataAsync();  // WARNING: Task returned but not awaited!
6    // The fetch starts but the method continues without waiting for it
7    Console.WriteLine("Done");  // prints before fetch completes
8}
9
10// CORRECT: Await the async call
11public async Task ProcessAsync()
12{
13    await FetchDataAsync();  // waits for completion
14    Console.WriteLine("Done");  // prints after fetch completes
15}
16
17// Fire-and-forget (intentional — rare, requires care)
18public void StartBackgroundWork()
19{
20    _ = FetchDataAsync();  // discard operator — explicitly intentional
21    // Exceptions from FetchDataAsync are silently lost!
22}
javascript
1// Same issue in JavaScript
2async function process() {
3    fetchData();         // WRONG: promise not awaited
4    await fetchData();   // CORRECT: waits for completion
5}

Confusion 2: Blocking on Async Code

csharp
1// WRONG: .Result blocks the thread and can deadlock in ASP.NET/WPF
2public string GetData()
3{
4    string result = FetchDataAsync().Result;  // BLOCKS — potential deadlock
5    return result;
6}
7
8// WRONG: .Wait() has the same problem
9public void ProcessData()
10{
11    FetchDataAsync().Wait();  // BLOCKS — potential deadlock
12}
13
14// CORRECT: async all the way
15public async Task<string> GetDataAsync()
16{
17    string result = await FetchDataAsync();  // non-blocking
18    return result;
19}

The deadlock occurs in environments with a SynchronizationContext (ASP.NET, WPF, WinForms). The .Result call blocks the context thread, and the await continuation needs that same thread to resume, creating a circular dependency.

Confusion 3: async void vs async Task

csharp
1// WRONG: async void — exceptions are unobservable
2public async void LoadData()
3{
4    var data = await FetchDataAsync();  // if this throws, the app crashes
5    Process(data);
6}
7
8// CORRECT: async Task — caller can await and catch exceptions
9public async Task LoadDataAsync()
10{
11    var data = await FetchDataAsync();
12    Process(data);
13}
14
15// Exception handling difference:
16try
17{
18    await LoadDataAsync();  // exception can be caught
19}
20catch (Exception ex)
21{
22    Console.WriteLine(ex.Message);
23}
24
25// async void exceptions cannot be caught by the caller
26// They go directly to the SynchronizationContext (crash in most cases)

async void should only be used for event handlers (e.g., button_Click). For all other cases, return Task or Task<T> so callers can await, catch exceptions, and compose operations.

Confusion 4: async Does Not Mean Parallel

csharp
1// Sequential — each call waits for the previous one
2public async Task SequentialAsync()
3{
4    var a = await FetchAAsync();  // waits for A
5    var b = await FetchBAsync();  // then waits for B
6    var c = await FetchCAsync();  // then waits for C
7    // Total time: A + B + C
8}
9
10// Parallel — all three start immediately
11public async Task ParallelAsync()
12{
13    var taskA = FetchAAsync();  // starts A
14    var taskB = FetchBAsync();  // starts B
15    var taskC = FetchCAsync();  // starts C
16
17    await Task.WhenAll(taskA, taskB, taskC);  // wait for all
18    // Total time: max(A, B, C)
19
20    var a = taskA.Result;  // safe after WhenAll
21    var b = taskB.Result;
22    var c = taskC.Result;
23}
javascript
1// JavaScript equivalent
2async function parallel() {
3    const [a, b, c] = await Promise.all([
4        fetchA(),
5        fetchB(),
6        fetchC(),
7    ]);
8}

Starting all tasks before awaiting them enables parallel execution. Task.WhenAll (C#) and Promise.all (JS) wait for all tasks to complete.

JavaScript-Specific Confusions

javascript
1// Confusion: async function always returns a Promise
2async function getData() {
3    return "hello";  // automatically wrapped in Promise.resolve("hello")
4}
5getData().then(value => console.log(value));  // "hello"
6
7// Confusion: await only works inside async functions
8function process() {
9    // const data = await fetchData();  // SyntaxError!
10}
11
12// Confusion: forEach does not await
13async function processItems(items) {
14    // WRONG: callbacks are not awaited
15    items.forEach(async (item) => {
16        await processItem(item);  // these run concurrently, not sequentially
17    });
18
19    // CORRECT: use for...of for sequential
20    for (const item of items) {
21        await processItem(item);
22    }
23
24    // CORRECT: use Promise.all for parallel
25    await Promise.all(items.map(item => processItem(item)));
26}

Common Pitfalls

  • Using .Result or .Wait() on async methods: These block the calling thread and cause deadlocks in ASP.NET, WPF, and WinForms due to the SynchronizationContext. Use await instead. If you must call async from sync code, use Task.Run(() => method()).GetAwaiter().GetResult() as a last resort.
  • Using async void instead of async Task: async void methods cannot be awaited, and their exceptions crash the application. Only use async void for event handlers. All other async methods should return Task or Task<T>.
  • Awaiting inside forEach expecting sequential execution: In JavaScript, Array.forEach does not await async callbacks — all iterations start concurrently. Use for...of for sequential execution or Promise.all(array.map(...)) for intentional parallelism.
  • Making everything async unnecessarily: Adding async/await to a method that just returns another task adds overhead from the state machine. If a method only calls one async method and returns its result, return the task directly: return FetchDataAsync() instead of return await FetchDataAsync().
  • Not handling exceptions from Task.WhenAll: Task.WhenAll throws only the first exception. Other exceptions are silently swallowed unless you inspect each task's .Exception property individually. Always check all task results after WhenAll.

Summary

  • async does not create threads — it enables non-blocking waits with await
  • Always await async calls or explicitly discard with _ = MethodAsync()
  • Never use .Result or .Wait() — they cause deadlocks in UI and web contexts
  • Return Task or Task<T>, never async void (except for event handlers)
  • Start multiple tasks before awaiting for parallel execution with Task.WhenAll
  • In JavaScript, use for...of (not forEach) for sequential async iteration

Course illustration
Course illustration

All Rights Reserved.