Making blocking calls to async methods transforms code that was intended to be asynchronous into a blocking operation. Doing so can
cause deadlocks and unexpected blocking of context threads.
According to the MSDN documentation:
The root cause of this deadlock is due to the way
awaithandles contexts. By default, when an incompleteTaskis awaited, the current “context” is captured and used to resume the method when theTaskcompletes. This “context” is the currentSynchronizationContextunless it’s null, in which case it’s the currentTaskScheduler. GUI and ASP.NET applications have aSynchronizationContextthat permits only one chunk of code to run at a time. When theawaitcompletes, it attempts to execute the remainder of theasyncmethod within the captured context. But that context already has a thread in it, which is (synchronously) waiting for theasyncmethod to complete. They’re each waiting for the other, causing a deadlock.
| To Do This … | Instead of This … | Use This |
|---|---|---|
Retrieve the result of a background task |
|
|
Wait for any task to complete |
|
|
Retrieve the results of multiple tasks |
|
|
Wait a period of time |
|
|
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait(); // Noncompliant
}
}
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
public static async Task TestAsync()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
await delayTask;
}
}
Thread.Sleep is also ignored when it is used in a non-async method. Task.Run or Task.Factory.StartNew are ignored because they don’t suffer from this deadlock issue