HCR063¶
Avoid sync-over-async around outbound HTTP.
Why¶
Blocking on outbound HTTP tasks with .Result, .Wait(), or .GetAwaiter().GetResult() can starve thread pools, deadlock synchronization contexts, and hide cancellation or timeout behavior. Prefer async all the way through the call path.
Bad¶
Better¶
Current Detection¶
The implementation reports .Result, .Wait(), and .GetAwaiter().GetResult() when they are applied directly to visible HttpClient async calls such as GetAsync(...), SendAsync(...), PostAsync(...), or System.Net.Http.Json's DeleteFromJsonAsync<T>(), GetFromJsonAsync<T>(), PostAsJsonAsync<T>(), PutAsJsonAsync<T>(), and PatchAsJsonAsync<T>(), or to visible local task variables whose latest declaration initializer or assignment comes from one of those calls through a cycle-safe local alias chain. Parentheses, null-forgiving operators, and explicit task casts are transparent around task values. It follows framework Task.ConfigureAwait(...) wrappers before blocking and also reports the same blocking shapes for common HttpContent async operations such as CopyToAsync(...), LoadIntoBufferAsync(...), ReadAsStringAsync(), and System.Net.Http.Json's ReadFromJsonAsync<T>().
Receivers and resolved method owners are validated with Roslyn type information when available, with syntactic fallback for unresolved snippets that visibly declare an HttpClient receiver. Awaited calls are skipped, arbitrary non-HTTP tasks are skipped, cyclic local task aliases are conservatively skipped, locals whose latest visible value is not an HTTP operation are skipped, resolved custom HttpClient lookalikes are skipped, and unrelated custom extensions on HttpClient, HttpContent, or HTTP task values are skipped even when they reuse a known method name such as ConfigureAwait.
The code fix replaces .Result, .GetAwaiter().GetResult(), or a parameterless .Wait(); statement with await when the nearest containing method, local function, or lambda is already async. It preserves member-access precedence with parentheses when needed and carries comments from a ConfigureAwait(...) chain onto the awaited expression. The fix is not offered for synchronous functions or .Wait(...) overloads because those cases can require signature, timeout, cancellation, and control-flow changes.
Suppression¶
Suppress only at deliberate synchronous boundaries where the caller cannot be made async and the blocking behavior has been reviewed for thread-pool, cancellation, and timeout impact.