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

时间:2016-04-21 15:34:40

标签: c# .net multithreading dictionary dotnet-httpclient

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

详细信息:
我正在运行一个Windows服务,它可以执行基于计划的连续ETL。其中一个数据同步线程偶尔会死掉,或者开始失控,并将处理器固定在100%。

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

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

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

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]

根据this article(以及我发现的其他人),词典的使用线程安全,并且无限循环是可能的(就像直接崩溃一样) )如果您以多线程方式访问字典。

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

通过.NET Reflector,出现,HttpClient使用字典来存储已经在" DefaultRequestHeaders"中配置的任何值。属性。因此,通过HttpClient发送的任何请求都会触发单个非线程安全字典的枚举(为了将默认标头添加到请求中),这可能会无限地旋转(或终止)所涉及的线程。发生腐败。

微软直言不讳地说HttpClient类是线程安全的。但在我看来,如果已将任何标头添加到HttpClient的DefaultRequestHeaders中,则不再适用。

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

但是,我正在寻找一些确认,我没有咆哮错误的树。如果这是正确的,它似乎是.NET框架中的一个错误,我自然会怀疑它。

对于这个罗嗦的问题感到抱歉,但感谢你提出的任何意见。

1 个答案:

答案 0 :(得分:1)

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

虽然问题 是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"是一种抽象方法 - 顺便说一句:唐& #39;在基类的构造函数中调用抽象方法......导致其他噩梦)

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

修复方法是将此代码置于双重检查"锁定"声明(虽然我将取消使用" DefaultRequestHeaders"属性,因为)。

简而言之,如果您在初始化HttpClient时要小心,我原来的问题就不会成为问题。

感谢您提供的清晰思想!