【发布时间】:2014-05-15 00:14:17
【问题描述】:
在通过 Web API 公开一些现有代码的过程中,我们遇到了很多死锁。我已经能够将问题提炼为这个非常简单的示例,该示例将永远挂起:
public class MyController : ApiController
{
public Task Get()
{
var context = TaskScheduler.FromCurrentSynchronizationContext();
return Task.FromResult(1)
.ContinueWith(_ => { }, context)
.ContinueWith(_ => Ok(DateTime.Now.ToLongTimeString()), context);
}
}
对我来说,这段代码似乎很简单。这可能看起来有点做作,但这只是因为我尝试尽可能地简化问题。似乎有两个像这样链接的 ContinueWiths 会导致死锁 - 如果我注释掉第一个 ContinueWith (无论如何它实际上并没有做任何事情),它会工作得很好。我也可以通过不提供特定的调度程序来“修复”它(但这对我们来说不是一个可行的解决方案,因为我们的真实代码需要在正确/原始线程上)。在这里,我将两个 ContinueWiths 放在一起,但在我们的实际应用程序中,发生了很多逻辑,而 ContinueWiths 最终来自不同的方法。
我知道我可以使用 async/await 重写这个特定的示例,它会简化事情并且似乎可以解决死锁。然而,我们在过去几年中编写了大量遗留代码——其中大部分是在 async/await 出现之前编写的,因此它大量使用 ContinueWith。如果可以避免的话,重写所有这些逻辑不是我们现在想做的事情。像这样的代码在我们遇到的所有其他场景(桌面应用程序、Silverlight 应用程序、命令行应用程序等)中都运行良好——只是 Web API 给我们带来了这些问题。
有什么方法可以通用解决这种死锁吗?我正在寻找一种解决方案,希望不会涉及重写所有 ContinueWith 以使用 async/await。
更新:
上面的代码是我控制器中的全部代码。我试图用最少的代码使这个可重现。我什至在一个全新的解决方案中做到了这一点。我所做的完整步骤:
- 从 Windows 7 上的 Visual Studio 2013 Update 1(带有 .NET Framework 4.5.1),使用 ASP.NET Web 应用程序模板创建一个新项目
- 选择 Web API 作为模板(在下一个屏幕上)
- 将自动创建的 ValuesController 中的 Get() 方法替换为我原始代码中给出的示例
- 按 F5 启动应用并导航到 ./api/values - 请求将永远挂起
- 我也尝试在 IIS 中托管网站(而不是使用 IIS Express)
- 我还尝试更新所有各种 Nuget 包,以便我掌握最新的一切
web.config 与模板创建的内容保持不变。具体来说,它有:
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
【问题讨论】:
-
我们需要实际的代码位来重现问题。
-
@YuvalItzchakov 我发布的代码实际上就是我编写的整个代码。我一直在使用标准模板在一个全新的项目中进行所有这些测试,而没有其他自定义代码。我已更新问题以包含重现此问题所需的确切步骤(希望如此)。
-
这似乎是我在这里描述的相同性质的死锁:stackoverflow.com/q/23062154/1768303
-
@Noseratio 我似乎确实遇到了与该问题中提到的问题类似的问题。但在那个问题上,僵局似乎是一个没有真正解决的次要问题。 :-(
-
@StephenMcDaniel,也许,this 可以提供帮助,但我自己没有尝试过。
标签: c# asp.net-web-api task-parallel-library deadlock