【发布时间】:2018-03-16 00:09:04
【问题描述】:
我在使用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 的代码的唯一解决方案?
【问题讨论】:
-
你有话题吗?你怎么没贴代码?
-
@CodingYoshi 我没有什么可以补充的,我还没有在问题中说。我可以看出
SynchronizationContext是基于进程转储在问题线程中设置的,但该线程没有托管堆栈跟踪,只有非托管的东西。 -
某事物正在启动 SynchronizationContext 然后等待它完成(这永远不会发生)的理论是可靠的。但是,如果您不向我们展示代码,我们只能告诉您这么多。
-
您是一直在等待所有任务,还是在某个时候阻塞?如果是后者,要么更改停止阻塞并等待,要么在您的任务上使用 ConfigureAwait(false):blog.stephencleary.com/2012/07/dont-block-on-async-code.html
-
@ejohnson 我能够通过使用 ConfigureAwait(false) 让它停止死锁。但我仍在试图弄清楚
SynchronizationContext是如何设置的,然后我将根据此决定使用ConfigureAwait(false)是否有意义。此外,我想确保添加ConfigureAwait(false)不会添加任何意外的副作用,即使它似乎有助于这种情况。
标签: c# .net async-await task-parallel-library synchronizationcontext