Anti-Pattern: Sync-over-Async (.Result / .Wait)
🔴 The Problem
Forcing an asynchronous operation to run synchronously by blocking the calling thread until it completes. This is a primary cause of Deadlocks in ASP.NET (legacy) applications.
The Mechanism
In ASP.NET (non-Core), the SynchronizationContext allows only one thread at a time to handle a request context.
AsyncMethod()starts and captures the current context..Resultblocks the main thread, waiting for the async method.AsyncMethod()finishes and tries to re-enter the main context to complete.- DEADLOCK: The context is blocked by
.Result(main thread is waiting), so the async method can't finish re-entering. The main thread waits forever for the async method, which is waiting for the main thread.
🚱 Real WEP Example
Identified in WepGoogleApiServiceFactory.cs:
// ❌ DEADLOCK RISK
// Blocking the thread to wait for an async network call
bool success = credential.RefreshTokenAsync(CancellationToken.None).Result;
💡 The Solution
Go "Async All the Way". If you call an async method, your method must be async and use await.
// ✅ NON-BLOCKING
// Releases thread to pool while waiting for Google
bool success = await credential.RefreshTokenAsync(CancellationToken.None);
Alternatives (Pragmatic Last Resort)
If you are in a legacy synchronous path that absolutely cannot be refactored to async (e.g., constructors, legacy event handlers), see the Safe Harbor Policy.
// ⚠️ Pragmatic Last Resort: Safe-ish, but still blocking
var result = Task.Run(() => credential.RefreshTokenAsync(...)).Result;
Forensic Detection
Use these commands to find risky sync-over-async blocks:
# Find usage of .Result or .Wait()
grep -r "\.Result" . --include="*.cs"
grep -r "\.Wait(" . --include="*.cs"