问题描述
使用HttpClient
发送http请求时,我遇到间歇性的死锁,有时它们从不返回我的代码中的await SendAsync
.我能够弄清楚在HttpClient
/HttpClientHandler
中内部处理请求的线程由于某种原因在死锁期间有一个SynchronizationContext
.我想弄清楚正在使用的线程如何以SynchronizationContext
结尾,而通常情况下它们没有一个.我会假设导致该SynchronizationContext
设置的任何对象也在Thread
上阻塞,从而导致死锁.
I am getting intermittent deadlocks when using HttpClient
to send http requests and sometimes they are never returning back to await SendAsync
in my code. I was able to figure out the thread handling the request internally in HttpClient
/HttpClientHandler
for some reason has a SynchronizationContext
during the times it is deadlocking. I would like to figure out how the thread getting used ends up with a SynchronizationContext
, when normally they don't have one. I would assume that whatever object is causing this SynchronizationContext
to be set is also blocking on the Thread
, which is causing the deadlock.
我能在TPL ETW事件中看到任何相关内容吗?
Would I be able to see anything relevant in the TPL ETW events?
如何解决此问题?
修改2:我注意到这些死锁的地方是Windows服务内部的wcf ServiceContract
(请参见下面的代码).引起问题的SynchronizationContext
实际上是WindowsFormsSynchronizationContext
,我认为这是由于某些控件被创建且未正确清理(或类似原因)引起的.我意识到几乎可以肯定,Windows服务中不应包含任何Windows窗体内容,我并不是说我同意它的使用方式.但是,我没有使用它编写任何代码,并且我不能仅仅琐碎地更改所有引用.
Edit 2:The place that I have been noticing these deadlocks is in a wcf ServiceContract
(see code below) inside of a windows service. The SynchronizationContext
that is causing an issue is actually a WindowsFormsSynchronizationContext
, which I assume is caused by some control getting created and not cleaned up properly (or something similar). I realize there almost certainly shouldn't be any windows forms stuff going on inside of a windows service, and I'm not saying I agree with how it's being used. However, I didn't write any of the code using it, and I can't just trivially go change all of the references.
编辑:这是我遇到问题的wcf服务的一般概念示例.这是一个简化的版本,而不是确切的代码:
Edit: here is an example of the general idea of the wcf service I was having a problem with. It's a simplified version, not the exact code:
[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());
}
}
}
在上述2个地方添加ConfigureAwait(false)
并不能完全解决我的问题,因为用于服务进入此处的wcf请求的线程池线程可能已经具有SynchronizationContext
. 在这种情况下,请求将完全通过整个GetAsync
方法进行处理并返回.但是,它仍然最终陷入了System.ServiceModel.Dispatcher.TaskMethodInvoker
的僵局,因为在该Microsoft代码中,它不使用ConfigureAwait(false)
,因此我想假设有充分的理由(以供参考):
Adding ConfigureAwait(false)
to the 2 places above didn't completely fix my problem because a threadpool thread used to service a wcf request coming into here may already have a SynchronizationContext
. In that case the request makes it all the way through this whole GetAsync
method and returns. However, it still ends up deadlocked in System.ServiceModel.Dispatcher.TaskMethodInvoker
, because in that microsoft code, it doesn't use ConfigureAwait(false)
and I want to assume there is a good reason for that (for reference):
var returnValueTask = returnValue as Task;
if (returnValueTask != null)
{
// Only return once the task has completed
await returnValueTask;
}
这确实是错误的,但是是否可以将其转换为使用APM(开始/结束)而不是使用任务"来解决?或者,是仅纠正未正确清理其SynchronizationContext
的代码的唯一修补程序吗?
It feels really wrong, but would converting this to using APM (Begin/End) instead of using Tasks fix this? Or, is the only fix to just correct the code that is not cleaning up its SynchronizationContext
properly?
推荐答案
更新:现在,无论在WCF应用程序中出于何种原因,我们都知道我们正在处理WindowsFormsSynchronizationContext
(请参阅注释). .看到死锁就不足为奇了,因为SyncContext的目的是在同一线程上运行所有延续.
Update: we now know we're dealing with a WindowsFormsSynchronizationContext
(see comments), for whatever reason in a WCF application. It's no surprise then to see deadlocks since the point of that SyncContext is to run all continuations on the same thread.
您可以尝试设置 WindowsFormsSynchronizationContext .AutoInstall 到false
.根据其文档,它的作用是:
You could try to to set WindowsFormsSynchronizationContext.AutoInstall to false
. According to its docs, what it does is:
假设有人在您的应用中的某个位置创建了WindowsForms控件,则可能是您遇到的问题,并且可以通过禁用此设置来解决.
Assuming someone creates a WindowsForms control somewhere in your app, then that might be your issue and would potentially be solved by disabling this setting.
摆脱现有SynchronizationContext
的一种选择是将其替换为null,然后恢复它(如果您还不错的话).此文章描述这种方法并提供了方便的 SynchronizationContextRemover
您可以使用的实施方式.
An alternative to get rid of an existing SynchronizationContext
would be to just overwrite it with null, and later restoring it (if you're nice). This article describes this approach and provides a convenient SynchronizationContextRemover
implementation you could use.
但是,如果SyncContext是由您使用的某些库方法创建的,则这可能行不通.我不知道一种防止SyncContext被覆盖的方法,因此设置虚拟上下文也无济于事.
However, this probably won't work if the SyncContext is created by some library methods you use. I'm not aware of a way to prevent a SyncContext from being overwritten, so setting a dummy context won't help either.
您确定SynchronizationContext
实际上是错误的吗?
Are you sure the SynchronizationContext
is actually at fault here?
此 MSDN杂志文章:
默认的SynchronizationContext将其异步委托排队到ThreadPool,但是直接在调用线程上执行其同步委托.因此,其上下文涵盖所有ThreadPool线程以及任何调用Send的线程.上下文借用"线程调用Send,将它们带入上下文,直到委托完成.从这个意义上讲,默认上下文可以包括进程中的任何线程.
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.
默认的SynchronizationContext应用于ThreadPool线程,除非代码由ASP.NET托管.除非子线程设置自己的SynchronizationContext,否则默认的SynchronizationContext也将隐式应用于显式子线程(Thread类的实例).
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
是默认值,则应该没问题(或者,您将很难避免使用它).
If the SynchronizationContext
you are seeing is the default one, it should be fine (or rather, you will have a very hard time to avoid it being used).
您不能提供有关所涉及内容的更多详细信息/代码吗?
Can't you provide more details / code about what's involved?
在您的代码中对我来说立即可疑的一件事(尽管可能完全没问题)是您有一个using
块,该块捕获了request
中的静态WebOperationContext.Current
,这两个都将被捕获.生成异步状态机.再说一次,可能很好,但是如果WebOperationContext
One thing that looks immediately suspicious to me in your code (though it may be completely fine) is that you have a using
block that captures a static WebOperationContext.Current
in request
, which will both be captured by the generated async state machine. Again, might be fine, but there's a lot of potential for deadlocks here if something waits on WebOperationContext
这篇关于如何修复具有SynchronizationContext的线程池线程上的死锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!