c# - 如何修复具有 SynchronizationContext 的线程池线程上的死锁?

标签 c# .net async-await task-parallel-library synchronizationcontext

我在使用 HttpClient 发送 http 请求时遇到间歇性死锁,有时它们永远不会返回到我的代码中的 await SendAsync。由于某种原因,我能够在 HttpClient/HttpClientHandler 中找出处理内部请求的线程,在死锁期间有一个 SynchronizationContext。我想弄清楚被使用的线程是如何以 SynchronizationContext 结束的,而通常情况下它们没有。我会假设导致此 SynchronizationContext 被设置的任何对象也在 Thread 上阻塞,这导致了死锁。

我能在 TPL ETW 事件中看到任何相关信息吗?

我该如何解决这个问题?



编辑 2: 我一直注意到这些死锁的地方是在 Windows 服务内的 wcf ServiceContract(参见下面的代码)中。导致问题的 SynchronizationContext 实际上是 WindowsFormsSynchronizationContext,我认为这是由于某些控件被创建但未正确清理(或类似的东西)引起的。我意识到 Windows 服务中几乎肯定不应该有任何 Windows 窗体的东西,我并不是说我同意它的使用方式。但是,我没有使用它编写任何代码,我不能随便更改所有引用。

编辑:这是我遇到问题的 wcf 服务的一般概念示例。这是一个简化版本,而不是确切的代码:

[ServiceContract]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
internal class SampleWcfService
{
    private readonly HttpMessageInvoker _invoker;

    public SampleWcfService(HttpMessageInvoker invoker)
    {
        _invoker = invoker;
    }
    
    [WebGet(UriTemplate = "*")]
    [OperationContract(AsyncPattern = true)]
    public async Task<Message> GetAsync()
    {
        var context = WebOperationContext.Current;
        using (var request = CreateNewRequestFromContext(context))
        {
            var response = await _invoker.SendAsync(request, CancellationToken.None).ConfigureAwait(false);
            var stream = response.Content != null ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
            return StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
        }
    }
}

ConfigureAwait(false) 添加到上面的两个地方并没有完全解决我的问题,因为用于服务进入此处的 wcf 请求的线程池线程可能已经有一个 SynchronizationContext在这种情况下,请求会一直通过整个 GetAsync 方法并返回。但是,它仍然在 System.ServiceModel.Dispatcher.TaskMethodInvoker 中陷入僵局,因为在那个微软代码中,它没有使用 ConfigureAwait(false) 我想假设这是有充分理由的(for reference):

var returnValueTask = returnValue as Task;

if (returnValueTask != null)
{
    // Only return once the task has completed                        
    await returnValueTask;
}

感觉真的不对,但是将其转换为使用 APM(开始/结束)而不是使用任务会解决这个问题吗?或者,唯一的修复方法是更正未正确清理其 SynchronizationContext 的代码吗?

最佳答案

更新:我们现在知道我们正在处理 WindowsFormsSynchronizationContext(请参阅评论),无论出于何种原因在 WCF 应用程序中。看到死锁也就不足为奇了,因为 SyncContext 的目的是在同一线程上运行所有延续。

您可以尝试设置 WindowsFormsSynchronizationContext.AutoInstallfalse。根据它的文档,它的作用是:

Gets or sets a value indicating whether the WindowsFormsSynchronizationContext is installed when a control is created

假设有人在您的应用中某处创建了一个 WindowsForms 控件,那么这可能就是您的问题,并且可能会通过禁用此设置来解决。

另一种方法摆脱现有的 SynchronizationContext 是用 null 覆盖它,然后再恢复它(如果你愿意的话)。这article描述了这种方法并提供了一个方便的 SynchronizationContextRemover您可以使用的实现。

但是,如果 SyncContext 是由您使用的某些库方法创建的,则这可能不起作用。我不知道有什么方法可以防止 SyncContext 被覆盖,因此设置虚拟上下文也无济于事。


您确定 SynchronizationContext 实际上是这里的错误吗?

从这里MSDN magazine article :

Default (ThreadPool) SynchronizationContext (mscorlib.dll: System.Threading)
The default SynchronizationContext is a default-constructed SynchronizationContext object. By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext.

The default SynchronizationContext queues its asynchronous delegates to the ThreadPool but executes its synchronous delegates directly on the calling thread. Therefore, its context covers all ThreadPool threads as well as any thread that calls Send. The context “borrows” threads that call Send, bringing them into its context until the delegate completes. In this sense, the default context may include any thread in the process.

The default SynchronizationContext is applied to ThreadPool threads unless the code is hosted by ASP.NET. The default SynchronizationContext is also implicitly applied to explicit child threads (instances of the Thread class) unless the child thread sets its own SynchronizationContext.

如果您看到的 SynchronizationContext 是默认的,它应该没问题(或者更确切地说,您将很难避免使用它)。

您不能提供有关所涉及内容的更多详细信息/代码吗?

我在您的代码中立即发现的一件事(尽管它可能完全没问题)是您有一个 using block 捕获静态 WebOperationContext.Currentrequest 中,它们都将被生成的异步状态机捕获。同样,可能没问题,但是如果有东西在 WebOperationContext

上等待,这里很可能会出现死锁

关于c# - 如何修复具有 SynchronizationContext 的线程池线程上的死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49311276/

相关文章:

c# - 如何在固定语句中使用锯齿状数组?

c# - EPPlus 超链接到另一个工作表中的单元格

c# - 在连接字符串中处理反斜杠

.net - Jenkins、MSBuild 和 Web 部署 : Unable to create Web Package

c# - 系统参数异常 : The table type parameter must have a valid type name

c# - 如何从 Owin 获取原始 url?

c# - Xamarin.iOS异步等待方法不起作用

c# - 异步/等待异常和 Visual Studio 2013 调试输出行为

c# - await 运算符只能在异步方法中使用 - 但方法是异步的

.net - .Net 标准 DLL 项目的构建文件夹中缺少 Nuget Pkg Dll。 dll 不是复制本地的吗?在 Visual Studio 2019 中修复?