Whetstone.
Decode your feedbackDecode: sync-over-async and the dropped token
Module 12 · Lesson 112 min

Decode: sync-over-async and the dropped token

This module is different. You’ve learned the idioms; now you decode the actual review comments Rai received on Fusion PRs, so that the next time one lands, you already know what it means and how to write the thing right the first time. We start with the single most-repeated correctness note.

The comment (Mustafa Aleem, on PR #491 and again on #508):

“use await instead of .GetAwaiter().GetResult(); make this method async as well.” “Sync over async again, when it should be awaited.” “‘.GetAwaiter().GetResult()’ was reintroduced and should be removed.” (left twice)

And on the token:

“don’t have = default, cancellationTokens must always be provided.”

What he meant. Two distinct rules, both invisible to a JS developer because JS can’t express the mistake. First, sync-over-async: calling .Result or .GetAwaiter().GetResult() on a Task blocks the current thread until the IO finishes. Under load that starves the thread pool and can deadlock, and it silently discards cancellation. The fix is never to bridge; await the task and let async spread up the call chain (Module 7 covered the mechanism). Second, the required token: a CancellationToken cancellationToken with = default lets every caller quietly drop cancellation, so an abandoned request keeps running a Databricks query for nothing. Making the parameter required forces the token to be threaded from the endpoint all the way down.

What drew the comment, and the fix
// What Mustafa flagged: blocks a thread, discards cancellation.
var summary = _repo.LoadRevertSummary(key).GetAwaiter().GetResult();

// The fix: await, make the caller async, thread a required token down.
public async Task<FlightRevertSummary?> LoadRevertSummary(
    string key, CancellationToken cancellationToken)
{
    return await _repo.LoadRevertSummary(key, cancellationToken);
}
Practice

Try it yourself

Recall

Restate Mustafa's note as a rule

Turn the review comment into the principle you'll carry into design.

Mustafa wrote, more than once, "use await instead of .GetAwaiter().GetResult()" and "cancellationTokens must always be provided." State the two rules those comments encode, and why each matters in .NET but not in JS.

Reveal answer

Rule 1: never block on async (no .Result, .Wait(), .GetAwaiter().GetResult()); await it and make the caller async, because blocking holds a thread-pool thread and can deadlock. Rule 2: make CancellationToken a required parameter (no = default) and thread it to the IO, so a cancelled request actually stops work. Neither exists in JS: it's single-threaded so 'sync over async' isn't expressible, and cancellation is opt-in via AbortSignal rather than a threaded contract.

Quiz

Match the fix to the comment

Mustafa left the comment: "Sync over async again, when it should be awaited." Which change resolves it?

  1. AWrap the whole call in a try/catch block to be safe
  2. BAdd a = default value to the CancellationToken parameter
  3. CDelete the method
  4. DReplace .GetAwaiter().GetResult() with await and make the method async
Show answer

Correct answer: D — Replace .GetAwaiter().GetResult() with await and make the method async

The fix for sync-over-async is to await the call and let async cascade up: change the body to await, mark the method async, and return Task/Task<T>. Adding = default to a token is the opposite of what he asks elsewhere.

Sign in to track your progress →