After running into a "common" async deadlock and getting further understanding from Async Best Practices I tried simulating the issue in ASP.NET in an attempt to figure out why we have never ran into this issue before. The difference it seems is the fact that previously we are using the http client get async methods and that hasn't caused an issue.
public class DeadLockController : ApiController
{
/// <summary>
/// Does not result in deadlock - but why?
/// </summary>
[HttpGet]
public IHttpActionResult HttpDeadLock()
{
var client = new HttpClient();
return Ok( client.GetStringAsync( "https://www.google.com/" ).Result );
}
/// <summary>
/// Results in expected repeatable deadlock.
/// </summary>
[HttpGet]
public IHttpActionResult DeadLock()
{
var delayTask = DelayAsync().Result;
return Ok( delayTask );
}
/// <summary>
/// Does not result in deadlock.
/// </summary>
[HttpGet]
public async Task< IHttpActionResult > FixedDeadLock()
{
var delayTask = await DelayAsync();
return Ok( delayTask );
}
private async Task< int > DelayAsync()
{
await Task.Delay( 1000 );
return 0;
}
}
So if you were to make a call to api/DeadLock/DeadLock
you would run into the common deadlock as described in the article. This is expected/understood. api/DeadLock/FixedDeadLock
is the "correct" way of getting around this.
However making a call to api/DeadLock/HttpDeadLock
doesn't result in a dead lock even though they are conceptually the same and I'm not sure why? Why does HttpClient not run into this issue? theoretically Httpclient is doing a await internally.
The reason why api/DeadLock/HttpDeadLock is not dead-locking is because within that code you are not awaiting for any task.
You are instead blocking the thread by synchronosuly waiting on the task by calling Task.Result.
Dead-locks when implementing this sort of patterns usually come up with a combination of async-await and Task.Wait/Task.Result combination.