Skip to content

Commit 9762088

Browse files
CopilotBillWagner
andcommitted
Add synchronous access to async operations documentation section
Co-authored-by: BillWagner <[email protected]>
1 parent 8a818b7 commit 9762088

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

docs/csharp/asynchronous-programming/async-scenarios.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,71 @@ If your program needs the result of a task, write code that implements the `awai
167167
| _Continue when **all** tasks complete_ | `Task.WaitAll` | `await Task.WhenAll` |
168168
| _Continue after some amount of time_ | `Thread.Sleep` | `await Task.Delay` |
169169

170+
### Synchronous access to asynchronous operations
171+
172+
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.
173+
174+
> [!WARNING]
175+
> 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.
176+
177+
When you must block synchronously on a `Task`, here are the available approaches, listed from most to least preferred:
178+
179+
#### Use GetAwaiter().GetResult()
180+
181+
The `GetAwaiter().GetResult()` pattern is generally the preferred approach when you must block synchronously:
182+
183+
```csharp
184+
// When you cannot use await
185+
Task<string> task = GetDataAsync();
186+
string result = task.GetAwaiter().GetResult();
187+
```
188+
189+
This approach:
190+
191+
- Preserves the original exception without wrapping it in an `AggregateException`
192+
- Blocks the current thread until the task completes
193+
- Still carries deadlock risk if not used carefully
194+
195+
#### Use Task.Run for complex scenarios
196+
197+
For complex scenarios where you need to isolate the asynchronous work:
198+
199+
```csharp
200+
// Offload to thread pool to avoid context deadlocks
201+
string result = Task.Run(async () => await GetDataAsync()).GetAwaiter().GetResult();
202+
```
203+
204+
This pattern:
205+
206+
- Executes the asynchronous method on a thread pool thread
207+
- Can help avoid some deadlock scenarios
208+
- Adds overhead by scheduling work to the thread pool
209+
210+
#### Avoid Wait() and Result
211+
212+
These blocking approaches are discouraged:
213+
214+
```csharp
215+
// Discouraged: Wraps exceptions in AggregateException
216+
Task<string> task = GetDataAsync();
217+
task.Wait();
218+
string result = task.Result;
219+
```
220+
221+
Problems with `Wait()` and `Result`:
222+
223+
- Exceptions are wrapped in `AggregateException`, making error handling more complex
224+
- Higher deadlock risk
225+
- Less clear intent in code
226+
227+
#### Additional considerations
228+
229+
- **Deadlock prevention**: Be especially careful in UI applications or when using a synchronization context
230+
- **Performance impact**: Blocking threads reduces scalability
231+
- **Exception handling**: Test error scenarios carefully as exception behavior differs between patterns
232+
233+
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/).
234+
170235
### Consider using ValueTask type
171236

172237
When an asynchronous method returns a `Task` object, performance bottlenecks might be introduced in certain paths. Because `Task` is a reference type, a `Task` object is allocated from the heap. If a method declared with the `async` modifier returns a cached result or completes synchronously, the extra allocations can accrue significant time costs in performance critical sections of code. This scenario can become costly when the allocations occur in tight loops. For more information, see [generalized async return types](../language-reference/keywords/async.md#return-types).

0 commit comments

Comments
 (0)