C# HttpClient随机抛出System.NullReferenceException问题求助

错误详情:
System.NullReferenceException HResult=0x80004003 Message=Object reference not set to an instance of an object. Source=System.Net.Http StackTrace: at System.Net.Http.HttpConnection.WriteAsciiString(String s) at System.Net.Http.HttpConnection.WriteHeaderCollection(HttpHeaders headers, String cookiesFromContainer) at System.Net.Http.HttpConnection.WriteHeaders(HttpRequestMessage request, HttpMethod normalizedMethod) at System.Net.Http.HttpConnection.<SendAsync>d__57.MoveNext() at System.Net.Http.HttpConnection.<SendAsync>d__57.MoveNext() at System.Net.Http.HttpConnectionPool.<SendWithVersionDetectionAndRetryAsync>d__89.MoveNext() at System.Net.Http.DiagnosticsHandler.<SendAsyncCore>d__10.MoveNext() at System.Net.Http.Metrics.MetricsHandler.<SendAsyncWithMetrics>d__5.MoveNext() at System.Net.Http.RedirectHandler.<SendAsync>d__4.MoveNext() at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<<SendCoreAsync>g__Core|5_0>d.MoveNext() at Microsoft.Extensions.ServiceDiscovery.Http.ResolvingHttpDelegatingHandler.<SendAsync>d__4.MoveNext() at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<<SendCoreAsync>g__Core|5_0>d.MoveNext() at System.Net.Http.HttpClient.<<SendAsync>g__Core|83_0>d.MoveNext() at MyApiService.Clients.Party3rdClient.<GetAuthentication>d__7.MoveNext() in Party3rdClient.cs:
这个问题随机出现,且在其他HttpClient调用中也会发生。我通过依赖注入使用HttpClient,不确定是否注入方式有误(本人刚接触C#)。以下是相关代码:
Program.cs
builder.Services.Configure<Party3rdSettings>(builder.Configuration.GetSection("Party3rdSettings")); builder.Services.AddTransient<IParty3rdClient, Party3rdClient>(); builder.Services.AddHttpClient<Party3rdClient>(client => { client.Timeout = TimeSpan.FromMinutes(5); });
IParty3rdClient.cs
namespace MyApiService.Clients.Interfaces { public interface IParty3rdClient { public Task<string> GetAuthentication(AuthFields authFields); } }
Part3rdClient.cs
namespace MyApiService.Clients { public class Part3rdClient: IPart3rdClient { private readonly Party3rdSettings _party3rdSettings; private readonly HttpClient _client; private static string Authentication = string.Empty; private static readonly JsonSerializerOptions jsonOptions = new () { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; public Part3rdClient(IOptions<Party3rdSettings > party3rdSettings, HttpClient client) { _party3rdSettings= party3rdSettings.Value; _client = client; } public async Task<string> GetAuthentication(AuthFields authFields) { try { using var request = new HttpRequestMessage(HttpMethod.Post, party3rdSettings.AuthConnectionString); var authenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{party3rdSettings.UserName}:{party3rdSettings.Password}")); _client.DefaultRequestHeaders.Clear(); var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString)); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authenticationString); _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); string body = JsonSerializer.Serialize(authFields, jsonOptions); request.Content = new StringContent(body, Encoding.UTF8, "application/json"); HttpResponseMessage response = await _client.SendAsync(request); if (response != null && response.IsSuccessStatusCode) { Debug.WriteLine("Authenticating 3rd Party. Successful!"); return await response.Content.ReadAsStringAsync(); } else if (response != null) { Debug.WriteLine("Error Authenticating 3rd Party. HttpStatusCode: " + response.StatusCode + ", message: " + response.RequestMessage); return "Unauthorized"; } } catch (HttpRequestException e) { Debug.WriteLine("3rd Party GetAuthentication() Exception :{0} ", e.Message); } catch (Exception ex) { Debug.WriteLine("3rd Party GetAuthentication() Exception :{0} ", ex.Message); } return string.Empty; } } }
调用代码
// 后续可考虑缓存认证令牌以避免重复调用 if (string.IsNullOrEmpty(Authentication)) Authentication = await GetAuthentication(new AuthFields("Something", "More")); if (!string.IsNullOrEmpty(Authentication) && Authentication != "Unauthorized") {
我是否在HttpClient配置或依赖注入中遗漏了什么?这是否是IDE的bug?该问题并非持续出现,恳请帮忙排查解决。
问题根源与解决办法
服务注册重复引发冲突
你手动注册了AddTransient<IParty3rdClient, Party3rdClient>,又通过AddHttpClient<Party3rdClient>注册了一次,这会导致框架生成两个不同的服务实例,引发不可预期的异常。修正:移除手动的
AddTransient,改用AddHttpClient直接关联接口:builder.Services.Configure<Party3rdSettings>(builder.Configuration.GetSection("Party3rdSettings")); builder.Services.AddHttpClient<IParty3rdClient, Party3rdClient>(client => { client.Timeout = TimeSpan.FromMinutes(5); });全局修改DefaultRequestHeaders引发并发问题
你在方法中修改HttpClient的全局默认头(Clear()、设置Authorization等),当多个请求并发执行时,会导致头信息被覆盖或出现空引用,这是随机异常的核心原因。修正:直接在
HttpRequestMessage上设置请求头,不修改全局默认值:using var request = new HttpRequestMessage(HttpMethod.Post, _party3rdSettings.AuthConnectionString); var authenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_party3rdSettings.UserName}:{_party3rdSettings.Password}")); // 仅为当前请求设置头 request.Headers.Authorization = new AuthenticationHeaderValue("Basic", authenticationString); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); string body = JsonSerializer.Serialize(authFields, jsonOptions); request.Content = new StringContent(body, Encoding.UTF8, "application/json"); HttpResponseMessage response = await _client.SendAsync(request);静态变量的线程安全问题
静态变量Authentication没有线程保护,多线程并发读写会导致竞态条件,可能引发异常或读取到无效值。修正:使用
lock块保证线程安全,或改用框架自带的IMemoryCache存储令牌(自带线程安全机制):private static readonly object _authLock = new object(); // 读取/写入时加锁 lock(_authLock) { if (string.IsNullOrEmpty(Authentication)) Authentication = await GetAuthentication(new AuthFields("Something", "More")); }配置项的空值验证
若Party3rdSettings中的配置项为空,会导致后续操作抛出异常。建议在构造函数中提前验证:public Part3rdClient(IOptions<Party3rdSettings> party3rdSettings, HttpClient client) { _party3rdSettings = party3rdSettings.Value ?? throw new ArgumentNullException(nameof(party3rdSettings)); if(string.IsNullOrEmpty(_party3rdSettings.AuthConnectionString)) throw new InvalidOperationException("未配置AuthConnectionString"); if(string.IsNullOrEmpty(_party3rdSettings.UserName)) throw new InvalidOperationException("未配置UserName"); if(string.IsNullOrEmpty(_party3rdSettings.Password)) throw new InvalidOperationException("未配置Password"); _client = client ?? throw new ArgumentNullException(nameof(client)); }
总结
这个问题不是IDE的bug,主要是并发修改全局请求头和服务注册重复导致的随机异常。按照上述步骤修正后,即可解决问题。
内容的提问来源于stack exchange,提问作者Age of Empires

