.NET Framework 4.6.2 HttpClient SendAsync间歇抛出空引用异常求助
问题本质定位
从异常栈可以明确,空引用并非发生在业务代码逻辑中,触发点全部在System.Net.Http组件内部:
- 异常触发链路为:
SendAsync→ 准备WebRequest实例 → 给请求绑定请求头 → 读取已存储的请求头值 → 调用头解析器的ToString方法时,读取到了null值,触发未处理的空引用。 - 结合.NET Framework 4.6.2的组件版本特性,这个偶发异常完全匹配内置
System.Net.Http.dll的已知并发缺陷:长期复用的HttpClient实例被多线程并发调用时,底层请求头的存储字典未实现线程安全保护,在多线程同时读写字典值、触发字典内部结构调整时,会出现短时间的null值读取,最终引发空引用。 - 可以排除业务层token生成逻辑的问题:如果
GenerateToken返回null,调用request.Headers.Add("Server-Token", null)时会在参数校验阶段直接抛出ArgumentException,不会延迟到SendAsync内部准备请求的环节才触发空引用。
排查验证方向
- 首先确认
_httpClient的生命周期:如果是跨请求复用的单例实例、被多个业务线程并发调用,基本可以锁定是上述并发竞态问题。 - 检查是否存在跨线程修改
_httpClient.DefaultRequestHeaders的代码:多线程并发调用DefaultRequestHeaders.Add/Remove方法,是触发这个竞态最常见的场景。 - 排查是否在应用启动后动态修改过请求头集合:比如初始化完成后才往默认请求头里加公共参数,没有做锁保护。
修复方案
- 根治方案:通过NuGet安装适配.NET Framework 4.6.2的新版
System.Net.Http包,替换框架内置的旧版本组件。该并发缺陷在4.7.2及以上版本的组件中已经完成了线程安全修复,安装后会自动生成程序集绑定重定向,不需要升级整个.NET Framework版本。 - 低改动规避方案:
- 给
_httpClient的并发调用增加信号量锁保护,同一时间只允许一个线程执行SendAsync,从根源上避免底层字典的并发读写。 - 如果不能给所有调用加锁,可以给涉及该异常的调用逻辑增加一层临时重试:捕获到对应栈帧的
NullReferenceException时,重新发起一次请求即可。因为竞态触发的概率极低,重试一次基本就能成功。
- 给
- 顺带修复代码逻辑漏洞:当前
GetPersonAsync方法不管请求成功、失败,最后都返回null,成功分支里序列化的结果没有赋值返回,属于逻辑漏写。
内容的提问来源于stack exchange,提问作者Walter Pereira de Souza Junior
相关产品推荐
相关产品推荐

