diff --git a/docs/csharp/asynchronous-programming/async-scenarios.md b/docs/csharp/asynchronous-programming/async-scenarios.md index 7246ade88a18b..fa2acf9011391 100644 --- a/docs/csharp/asynchronous-programming/async-scenarios.md +++ b/docs/csharp/asynchronous-programming/async-scenarios.md @@ -188,6 +188,71 @@ Avoid writing code that depends on the state of global objects or the execution A recommended goal is to achieve complete or near-complete [Referential Transparency](https://en.wikipedia.org/wiki/Referential_transparency) in your code. This approach results in a predictable, testable, and maintainable codebase. +### Synchronous access to asynchronous operations + +In rare scenarios, you might need to block on asynchronous operations when the `await` keyword isn't available throughout your call stack. This situation commonly occurs in legacy codebases or when integrating asynchronous methods into synchronous APIs that can't be changed. + +> [!WARNING] +> Synchronous blocking on asynchronous operations can lead to deadlocks and should be avoided whenever possible. The preferred solution is to use `async`/`await` throughout your call stack. + +When you must block synchronously on a `Task`, here are the available approaches, listed from most to least preferred: + +#### Use GetAwaiter().GetResult() + +The `GetAwaiter().GetResult()` pattern is generally the preferred approach when you must block synchronously: + +```csharp +// When you cannot use await +Task task = GetDataAsync(); +string result = task.GetAwaiter().GetResult(); +``` + +This approach: + +- Preserves the original exception without wrapping it in an `AggregateException` +- Blocks the current thread until the task completes +- Still carries deadlock risk if not used carefully + +#### Use Task.Run for complex scenarios + +For complex scenarios where you need to isolate the asynchronous work: + +```csharp +// Offload to thread pool to avoid context deadlocks +string result = Task.Run(async () => await GetDataAsync()).GetAwaiter().GetResult(); +``` + +This pattern: + +- Executes the asynchronous method on a thread pool thread +- Can help avoid some deadlock scenarios +- Adds overhead by scheduling work to the thread pool + +#### Avoid Wait() and Result + +These blocking approaches are discouraged: + +```csharp +// Discouraged: Wraps exceptions in AggregateException +Task task = GetDataAsync(); +task.Wait(); +string result = task.Result; +``` + +Problems with `Wait()` and `Result`: + +- Exceptions are wrapped in `AggregateException`, making error handling more complex +- Higher deadlock risk +- Less clear intent in code + +#### Additional considerations + +- **Deadlock prevention**: Be especially careful in UI applications or when using a synchronization context +- **Performance impact**: Blocking threads reduces scalability +- **Exception handling**: Test error scenarios carefully as exception behavior differs between patterns + +For more detailed guidance on the challenges and considerations of synchronous wrappers for asynchronous methods, see [Should I expose synchronous wrappers for asynchronous methods?](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/). + ## Review the complete example The following code represents the complete example, which is available in the *Program.cs* example file.