Search code examples
c#asp.net.netasync-awaitdeadlock

ASP.NET Web API async controller method and deadlock


Please help me to understand why this code cause a deadlock? I have an asp.net web api application and I tried to make some controller method asynchronous.


    [HttpPost]
    [Authentication]
    public async Task<SomeDTO> PostSomething([FromBody] SomeDTO someDTO)
    {
        return await _service.DoSomething(someDTO);
    }

this is how looks the called service method:


    public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
    {
...
        var someTask = Task.Run(() => 
        {
            var entity = new SomeEntity(someDTO);
            return _repository.Create(entity);
        });
...
        var result = await someTask;
...
    }

And there is some globalhandler, that prints a response to a console.


    public class AppGlobalHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var resp = base.SendAsync(request, cancellationToken);
            Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{resp?.ConfigureAwait(false).GetAwaiter().GetResult()?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()}");
            return resp;
        }
    }

Looks like ConfigureAwait(false).GetAwaiter().GetResult() blocks the caller thread, but I supposed that ConfigureAwait(false) should avoid this, isn't it?


Solution

  • ConfigureAwait(false) would not help you here because it must be all the way down in the call stack (see more here) not at place where you wait synchronously, i.e. it depends rather on the implementation of base.SendAsync. If it acquired a lock on current thread it's too late to do something about it. It is also not recommended in ASP.net pipeline to continue responding on other thread after all (see discussion here and post here).

    Finally it is always a highly risky idea to wait synchronously in async context. If you need to read content, why not doing it like that:

     public class AppGlobalHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var resp = await base.SendAsync(request, cancellationToken);
            var content = resp?.Content != null 
               ? (await resp.Content.ReadAsStringAsync()) 
               : string.Empty; 
            Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{content}");
            return resp;
        }
    }