-
Notifications
You must be signed in to change notification settings - Fork 6k
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So is it rare or is it common? :) |
||||||||||||||
|
||||||||||||||
> [!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: | ||||||||||||||
|
||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we list the approaches here with links to the headings? |
||||||||||||||
#### 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 | ||||||||||||||
Comment on lines
+212
to
+214
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
#### 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 | ||||||||||||||
Comment on lines
+227
to
+229
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
#### Avoid Wait() and Result | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it's listed as one of the "approaches", albeit the least preferred one, it seems weird to start the heading with "Avoid". |
||||||||||||||
|
||||||||||||||
These blocking approaches are discouraged: | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably update the instructions file to say that it's better to put info in the Markdown than in code comments (since it's not localized). |
||||||||||||||
|
||||||||||||||
```csharp | ||||||||||||||
// Discouraged: Wraps exceptions in AggregateException | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
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 | ||||||||||||||
Comment on lines
+244
to
+246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
#### 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 | ||||||||||||||
Comment on lines
+250
to
+252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
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. | ||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.