作为通过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 中的完整代码。我试图用最少的代码来重现这一点。我什至在全新的解决方案中做到了这一点。我所做的全部步骤:
  • 从Windows 7(带有.NET Framework 4.5.1)上的Visual Studio 2013 Update 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>
    

    最佳答案

    请尝试以下操作(未经测试)。它基于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/

    10-11 19:42
    查看更多