In my ASP.NET Core application I have a a seemingly very simple action. It awaits some value from an asynchronous method and then returns it as an OK-result:
public async Task<IActionResult> GetNextCommand()
{
var command = await LongPollManager.Instance.GetNextCommand(HttpContext.RequestAborted);
return Ok(command);
}
When I call this route with some HTTP client I can verify in the debugger that this asynchronous method returns the desired value and passes it to the Ok method:
If I let the debugger continue I would expect to get the result in my HTTP client. But the client never receives a response.
When I then break the debugger I can see that the thread is blocked on some internal lock. You can see this in the current screenshot:
This behavior can be seen only since I made some changes in my LongPollManager
class (which is actually quite complex and uses TaskCompletionSource
s and ConcurrentDictionarie
s, and SemaphoreSlim
s internally).
The thing that puzzles me is that it's actually not my own GetNextCommand
method which is blocking, but the blocking seems to happen inside ASP.NET Core. Once execution is in line 29 and I got my command
object, all the complicated asynchronous stuff of my LongPollManager
class is over and I don't see how anything I change in LongPollManager
can prevent ASP.NET Core from properly finishing the request.
What could it be that ASP.NET Core is waiting here for? How can my code (which runs to line 29 without a deadlock) cause such a deadlock situation?
As mentioned in the comments
But with my latest changes it also contains a public
Task
property. Do you think this could have any side-effects when ASP.NET Core tries to serialize it?
Yes it will.
The framework will try to invoke the property to get the value for serialization. And as the property returns a Task
, will most likely try to serialize the task's .Result
property synchronously which would lead to your dead lock.
Mixing async and blocking calls like .Result
and .Wait()
can cause deadlocks and should be avoided.
Reference Async/Await - Best Practices in Asynchronous Programming
Actions should return simple POCOs that have no side effects when serialized.
The public Task
property should be hidden from the serailizer, ignored via attributes or should be converted to methods that do not get invoked when the model is serialized.