在 HttpClient.SendAsync() 之后无法访问已释放的对象
Posted
技术标签:
【中文标题】在 HttpClient.SendAsync() 之后无法访问已释放的对象【英文标题】:Cannot access a disposed object after HttpClient.SendAsync() 【发布时间】:2019-11-21 17:32:58 【问题描述】:我们有一个 dotnet-core 2.1 web-api 项目设置如下。
我们正在尝试将 MediatR 与 HttpClient 一起使用,并在 HttpMagicService 中为不同的响应发送事件。在调用 HttpClient.SendAsync()
后尝试使用注入服务时出现问题。
IMagicService
和 MediatR 的初始设置。
public void ConfigureServices(IServiceCollection services)
// other services configured above
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestLoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPerformanceBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
services.AddMediatR(typeof(ApplicaitonCommand).GetTypeInfo().Assembly);
services.AddHttpClient<IMagicService, HttpMagicService>();
IMagicService
的实现。
public class HttpMagicService : IMagicService
private readonly IMediator mediator;
private readonly HttpClient httpClient;
public HttpMagicService(IMediator mediator, HttpClient httpClient)
this.mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
public async Task AddAsync(Magic data, Metadata metadata)
// This one works
await this.mediator.Publish(new MagicAddInitIntegrationEvent(data));
var url = metadata.RemoteUri + "/command/AddMagic";
using (var request = new HttpRequestMessage(HttpMethod.Post, url))
try
var httpContent = CreateHttpContent(data);
request.Content = httpContent;
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", metadata.AuthKey);
// httpClient clears the ResolvedServices?!
var response = await this.httpClient.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
// This one fails
await this.mediator.Publish(new MagicAddIntegrationEvent(data));
else
await this.mediator.Publish(new MagicAddFailedIntegrationEvent(data));
catch (Exception ex)
await this.mediator.Publish(new MagicAddFailedIntegrationEvent(meeting, ex));
private static HttpContent CreateHttpContent(object content)
// ...
对于我们的第一次调用await this.mediator.Publish(new MagicAddInitIntegrationEvent(data));
,事件已发布并且处理程序可以处理该事件。
查看this.mediator
,我们看到有一个已解决的服务。
当我们运行var response = await this.httpClient.SendAsync(request).ConfigureAwait(false);
时,似乎httpClient 只是决定为每个人处理所有服务。 ????
在这行之后查看this.mediator
时,我们看到所有内容都已清理完毕。
由于没有服务,调用 await this.mediator.Publish(new MagicAddIntegrationEvent(data));
将无法运行,我们得到一个异常。
这是this.httpClient.SendAsync
的预期行为吗?我们应该以另一种方式来做吗?
似乎可行的一种解决方案是忽略设置services.AddHttpClient<IMagicService, HttpMagicService>();
,而是为HttpClient
设置单例服务,这似乎可行,但我不确定这是否是正确的方法。
解决方案
使用了 Panagiotis Kanavos 的建议并做了一个作用域 httpClient,所以我的解决方案变成了这个。
public void ConfigureServices(IServiceCollection services)
// other services configured above
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestLoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPerformanceBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
services.AddMediatR(typeof(ApplicaitonCommand).GetTypeInfo().Assembly);
services.AddScoped<HttpClient>();
如果尝试在范围内使用 IMediator 而不使用 var mediator= scope.ServiceProvider.GetRequiredService<IMediator>();
,则会出现与之前相同的问题。所以我错过了 DI 容器的一些范围问题。
public class HttpMagicService : IMagicService
private readonly IMediator mediator;
private readonly IServiceProvider services;
public HttpMagicService(IServiceProvider services)
this.services = services?? throw new ArgumentNullException(nameof(services));
public async Task AddAsync(Magic data, Metadata metadata)
using (var scope = Services.CreateScope())
var httpClient = scope.ServiceProvider.GetRequiredService<HttpClient>();
var mediator= scope.ServiceProvider.GetRequiredService<IMediator>();
...
【问题讨论】:
评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:当单例服务尝试使用范围服务时,经常会发生此类异常。 This question about MediatR and scopes 可能是相关的。在任何情况下,HttpClientFactory 都会定期回收 HttpClient 实例(特别是 handler 实例),这意味着如果HttpMagicService
缓存 HttpClient 实例足够长的时间,它将在某个时候被释放。
在任何一种情况下,您都可以通过将 IServiceProvider 注入 HttpMagicService 并显式创建范围来使用 HttpClient。检查consuming a scoped service in a background task。
您可以注入 IServiceProvider 而不是 HttpClient,并在 AddAsync
方法内创建一个作用域,例如:
public class HttpMagicService : IMagicService
private readonly IMediator mediator;
private readonly IServiceProvider services;
public HttpMagicService(IMediator mediator, IServiceProvider services)
this.mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
this.services = services?? throw new ArgumentNullException(nameof(services));
public async Task AddAsync(Magic data, Metadata metadata)
using (var scope = Services.CreateScope())
var httpClient = scope.ServiceProvider.GetRequiredService<HttpClient>();
...
【讨论】:
如果实例的作用域是短暂的......你最终会耗尽机器中的套接字,你的 HttpClient 将因 SocketException 而崩溃。请记住,HttpClient 旨在在应用程序的整个生命周期内用作 SINGLE 实例。 您发布的文档中写道:“因此,HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量. 该问题将导致 SocketException 错误。"以上是关于在 HttpClient.SendAsync() 之后无法访问已释放的对象的主要内容,如果未能解决你的问题,请参考以下文章
HttpClient.SendAsync:500 - Windows 服务应用程序中的内部服务器错误
HttpClient.SendAsync 方法退出而不抛出异常
HttpClient.SendAsync 在 Xamarin.Forms Android 上引发 ObjectDisposedException,但在 UWP 上却没有