使用HttpClient
发送 http 请求时,我会遇到间歇性死锁,有时它们永远不会返回到我的代码中的await SendAsync
。我能够在HttpClient
/HttpClientHandler
中找出内部处理请求的线程,由于某种原因在死锁期间有SynchronizationContext
。我想弄清楚使用的线程如何以SynchronizationContext
结束,而通常他们没有。我假设导致设置此SynchronizationContext
的任何对象也在阻塞Thread
,从而导致死锁。
我能否在 TPL ETW 事件中看到任何相关内容?
如何对此进行故障排除?
编辑2:我注意到这些死锁的地方是在Windows服务内部的wcfServiceContract
(见下面的代码)。导致问题的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)
添加到上面的 2 个地方并没有完全解决我的问题,因为用于为进入此处的 wcf 请求提供服务的线程池线程可能已经具有SynchronizationContext
。在这种情况下,请求会一直完成整个GetAsync
方法并返回。但是,它仍然最终在System.ServiceModel.Dispatcher.TaskMethodInvoker
中陷入僵局,因为在微软代码中,它不使用ConfigureAwait(false)
,我想假设有一个很好的理由(供参考):
var returnValueTask = returnValue as Task;
if (returnValueTask != null)
{
// Only return once the task has completed
await returnValueTask;
}
感觉真的很不对劲,但是将其转换为使用APM(开始/结束)而不是使用任务可以解决此问题吗?或者,唯一的解决方法是更正未正确清理其SynchronizationContext
的代码?
更新:我们现在知道我们正在处理一个WindowsFormsSynchronizationContext
(请参阅注释),无论出于何种原因,在 WCF 应用程序中。看到死锁也就不足为奇了,因为 SyncContext 的重点是在同一线程上运行所有延续。
您可以尝试将 WindowsFormsSynchronizationContext.AutoInstall 设置为false
。根据其文档,它的作用是:
获取或设置一个值,该值指示在创建控件时是否安装了 WindowsFormsSynchronizationContext
假设有人在你的应用中的某个位置创建了 WindowsForms 控件,那么这可能是你的问题,并且可能会通过禁用此设置来解决。
摆脱现有SynchronizationContext
的另一种方法是用 null 覆盖它,然后恢复它(如果你很好的话)。本文介绍此方法,并提供可以使用的方便SynchronizationContextRemover
实现。
但是,如果 SyncContext 是由您使用的某些库方法创建的,则这可能不起作用。我不知道有什么方法可以防止 SyncContext 被覆盖,因此设置虚拟上下文也无济于事。
你确定SynchronizationContext
真的有错吗?
来自这篇 MSDN 杂志文章:
默认 (ThreadPool) SynchronizationContext(mscorlib.dll: System.Threading)默认
SynchronizationContext 是默认构造的 SynchronizationContext 对象。按照惯例,如果线程的当前 SynchronizationContext为 null,则它隐式具有默认的 SynchronizationContext。默认的 SynchronizationContext 将其异步委托排队到 ThreadPool,但直接在调用线程上执行其同步委托。因此,其上下文涵盖所有线程池线程以及调用 Send 的任何线程。上下文"借用"调用 Send 的线程,将它们引入其上下文,直到委托完成。从这个意义上说,默认上下文可以包含进程中的任何线程。
默认的 SynchronizationContext 将应用于 ThreadPool 线程,除非代码由 ASP.NET 托管。默认的同步上下文也隐式应用于显式子线程(线程类的实例),除非子线程设置自己的同步上下文。
如果您看到的SynchronizationContext
是默认的,那么它应该没问题(或者更确切地说,您将很难避免使用它)。
您不能提供有关所涉及的内容的更多详细信息/代码吗?
在您的代码中,对我来说立即可疑的一件事(尽管可能完全没问题)是,您有一个using
块,可以捕获request
中的静态WebOperationContext.Current
,这两者都将被生成的异步状态机捕获。同样,可能没问题,但如果有什么东西在等待WebOperationContext
,这里有很多死锁的可能性
试试下面;我在类似的案例中发现了进入异步兔子洞的成功。
var responsebytes = await response.Content.ReadAsByteArrayAsync();
MemoryStream stream = new MemoryStream(filebytes);
响应流变量。
希望对您有所帮助。