作为通过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(无论如何实际上并没有做任何事情),它将正常工作。我也可以通过不提供特定的调度程序来“修复”它(但这对我们来说不是可行的解决方案,因为我们的真实代码必须位于正确/原始的线程上)。在这里,我将两个ContinueWith彼此相邻放置,但是在我们的实际应用程序中,发生了很多逻辑,ContinueWith最终来自不同的方法。
我知道我可以使用async/await重新编写这个特定的示例,它可以简化事情,并且似乎可以解决死锁。但是,在过去的几年中,我们已经编写了大量的遗留代码-而且大多数代码是在异步/等待出现之前编写的,因此大量使用了ContinueWith。如果可以避免的话,我们现在不希望重新编写所有逻辑。这样的代码在我们遇到的所有其他场景(台式机应用程序,Silverlight应用程序,命令行应用程序等)中都可以正常工作-正是Web API导致了这些问题。
有什么可以解决这种僵局的方法吗?我正在寻找一种解决方案,希望它不涉及重写所有ContinueWith以使用async/await。
更新:
上面的代码是我 Controller 中的完整代码。我试图用最少的代码来重现这一点。我什至在全新的解决方案中做到了这一点。我所做的全部步骤:
web.config与模板创建的内容保持不变。具体来说,它具有以下功能:
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
最佳答案
请尝试以下操作(未经测试)。它基于AspNetSynchronizationContext.Send
同步执行回调的想法,因此不应导致the same deadlock。这样,我们在随机池线程上输入AspNetSynchronizationContext
:
public class MyController : ApiController
{
public Task Get()
{
// should be AspNetSynchronizationContext
var context = SynchronizationContext.Current;
return Task.FromResult(1)
.ContinueWith(_ => { }, TaskScheduler.Default)
.ContinueWith(_ =>
{
object result = null;
context.Send(__ => { result = Ok(DateTime.Now.ToLongTimeString()); },
null);
return result;
}, TaskScheduler.Default);
}
}
根据注释更新了,显然可以正常工作并消除了死锁。此外,我将在此解决方案的基础上构建一个自定义任务调度程序,并使用它代替
TaskScheduler.FromCurrentSynchronizationContext()
,而对现有代码库的更改很小。关于c# - Web API中的ContinueWith导致死锁,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23638886/