3

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.

Ashtonian
  • 4,371
  • 2
  • 18
  • 25
  • Change DelayAsync to be `await Task.Delay( 1000 ).ConfigureAwait(false);` and I bet `api/DeadLock/DeadLock` will nolonger deadlock either. Go read the "Configure Context" section of the article for some explanation. – Scott Chamberlain Dec 16 '15 at 17:16
  • @ScottChamberlain thanks - you can also fix it with using an await all the way up like I did in the FixedDeadLock() function also sourced from that article because of the context - what the question is though is why HttpClient does not cause this issue? – Ashtonian Dec 16 '15 at 18:58
  • 1
    FYI - not the answer to your question, but you should read: http://stackoverflow.com/q/22560971/634824 – Matt Johnson-Pint Dec 16 '15 at 19:02
  • Out of curiosity, how reliably can you reproduce the `api/deadlock/deadlock`? i.e. do you hit it with 100 concurrent requests? Internally `HttpClient` has `.ConfigureAwait(false)` all the way down to prevent such problems with "incorrect" use of the library – KCD Jun 07 '17 at 19:43
  • if you hit api/deadlock/deadlock it will hang because of the deadlock every time. – Ashtonian Jun 07 '17 at 21:37

1 Answers1

3

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.

hivo
  • 291
  • 2
  • 5
  • 2
    A key point that is not explicit in the answer that I want to stress is `GetStringAsync` does not use `await` internally, [it is using](https://github.com/dotnet/corefx/blob/e76774df88b497107e8289fce314ea149abc7811/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L163) a `TaskCompletionSource` to generate the task then just returns the task. – Scott Chamberlain Dec 16 '15 at 18:28
  • @ScottChamberlain the comment is the answer I was looking for :) Thanks – Ashtonian Dec 16 '15 at 19:03
  • 3
    @ScottChamberlain I'd say the real key is simply that it doesn't post its continuations to the current synchronization context. (Which it could do without using `await` or which could be avoided even when using `await`.) – Servy Dec 16 '15 at 19:11