Skip to content

Add documentation for synchronous access to asynchronous operations #47735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/csharp/asynchronous-programming/async-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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<string> 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.
Expand Down