ASP.NET 核心控制器中出现奇怪的死锁情况

Strange deadlock situation in ASP.NET Core Controller

在我的ASP.NET核心应用中我有一个看似很简单的动作。它等待来自异步方法的一些值,然后 returns 它作为 OK 结果:

public async Task<IActionResult> GetNextCommand()
{
    var command = await LongPollManager.Instance.GetNextCommand(HttpContext.RequestAborted);
    return Ok(command);
}

当我使用一些 HTTP 客户端调用此路由时,我可以在调试器中验证此异步方法 returns 所需的值并将其传递给 Ok 方法:

如果让调试器继续运行,我希望在我的 HTTP 客户端中得到结果。但是客户端从未收到响应。

然后当我中断调试器时,我可以看到线程被某个内部锁阻塞。您可以在当前屏幕截图中看到这一点:

这种行为只有在我对 LongPollManager class 进行了一些更改后才能看到(这实际上非常复杂,并且使用了 TaskCompletionSources 和 ConcurrentDictionaries,和 SemaphoreSlims 内部)。

让我疑惑的是,阻塞的其实不是我自己的GetNextCommand方法,而是阻塞似乎发生在ASP.NET核心内部。一旦在第 29 行执行并且我得到了我的 command 对象,我的 LongPollManager class 的所有复杂异步内容都结束了,我看不出我在 [=12] 中有什么改变=] 可以阻止 ASP.NET 核心正确完成请求。

ASP.NET核心在这里等待的究竟是什么?我的代码(运行到第 29 行没有死锁)怎么会导致这种死锁情况?

如评论所述

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?

会的。

框架将尝试调用 属性 来获取序列化的值。由于 属性 return 是 Task,很可能会尝试同步序列化任务的 .Result 属性,这会导致死锁。

.Result.Wait() 这样混合异步和阻塞调用会导致死锁,应该避免。

引用Async/Await - Best Practices in Asynchronous Programming

动作应该 return 序列化时没有副作用的简单 POCO。

public Task 属性 应该从序列化程序中隐藏,通过属性忽略,或者应该转换为在序列化模型时不调用的方法。