繁体   English   中英

.NET:由于字典,HttpClient中100%的CPU使用率?

[英].NET: 100% CPU usage in HttpClient because of Dictionary?

简短问题:
在使用单例.NET HttpClient时,是否有其他人遇到问题,其中应用程序将处理器固定在100%,直到重新启动?

细节:
我正在运行Windows服务,该服务执行基于计划的连续ETL。 数据同步线程之一有时只是死掉,或者开始失控并以100%的速率固定处理器。

在有人重新启动服务(标准修复程序)并能够获取转储文件之前,我很幸运地看到了这种情况的实时发生。

将其加载到WinDbg中(带有SOS和SOSEX),我发现大约有15个线程(主处理线程的子任务)都以相同的堆栈跟踪运行。 但是,似乎没有任何僵局。 IE的高利用率线程正在运行,但从未完成。

相关的堆栈跟踪段如下(地址省略):

System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].TryGetValue(System.__Canon, System.__Canon ByRef)
System.Net.Http.Headers.HttpHeaders.ContainsParsedValue(System.String, System.Object)
System.Net.Http.Headers.HttpGeneralHeaders.get_TransferEncodingChunked()
System.Net.Http.Headers.HttpGeneralHeaders.AddSpecialsFrom(System.Net.Http.Headers.HttpGeneralHeaders)
System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(System.Net.Http.Headers.HttpHeaders)
System.Net.Http.HttpClient.SendAsync(System.Net.Http.HttpRequestMessage, System.Net.Http.HttpCompletionOption, System.Threading.CancellationToken)
...
[Our Application Code]

根据这篇文章 (以及我发现的其他文章 ),词典的使用不是线程安全的,并且如果您以多线程方式访问字典,则可能会发生无限循环(如直接崩溃)。

但是我们的应用程序代码未显式使用字典。 那么堆栈跟踪中提到的字典在哪里?

通过.NET Reflector, 似乎 HttpClient使用字典来存储“ DefaultRequestHeaders”属性中已配置的所有值。 因此,通过HttpClient发送的任何请求都会触发一个单例,非线程安全的字典的枚举(以便将默认标头添加到该请求中),如果出现以下情况,它可能无限地旋转(或杀死)所涉及的线程:发生损坏。

Microsoft直言不讳地说HttpClient类是线程安全的。 但是在我看来,如果将任何标头添加到HttpClient的DefaultRequestHeaders中,这不再是正确的。

我的分析似乎表明这是真正的根本问题,一个简单的解决方法是根本不使用DefaultRequestHeaders,在该情况下HttpClient可以以多线程方式使用。

但是,我正在寻找一些确认,确认我没有吠错树。 如果这是正确的话,那似乎是.NET框架中的错误,我自然会对此表示怀疑。

很抱歉出现罗word的问题,但感谢您的宝贵意见。

感谢所有的评论; 他们让我沿着不同的方向思考,并帮助我找到了问题的最终根本原因。

尽管此问题由于DefaultRequestHeaders的后备字典中的损坏导致的,但真正的罪魁祸首是HttpClient对象的初始化代码:

private HttpClient InitializeClient()
{
    if (_client == null)
    {
        _client = GetHttpClient();
        _client.DefaultRequestHeaders.Accept.Clear();
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        SetBaseAddress(BaseAddress);
    }
    return _client;
}

我说HttpClient是一个单例,这在某种程度上是不正确的。 它是作为一个单实例创建的,在一个工作单元的多个线程之间共享,并在工作完成时进行处理。 下次必须完成此特定任务时,将启动一个新实例。

每次要发送请求时,都会调用上面的“ InitializeClient”方法,并且由于“ _client”字段在第一次运行后不为空,因此该方法只是短路。

(请注意,这不是在对象的构造函数中完成的,因为它是一个抽象类,而“ GetHttpClient”是一个抽象方法-顺便说一句:永远不要在基类的构造函数中调用抽象方法...其他噩梦)

当然,这显然不是线程安全的,并且由此产生的行为是不确定的。

解决方法是将这段代码放在经过双重检查的“ lock”语句之后(尽管我会因为任何原因而避免使用“ DefaultRequestHeaders”属性)。

简而言之,如果您谨慎地初始化HttpClient,我的原始问题就永远不会成为问题。

感谢您提供的思路清晰!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM