在 HttpClient.SendAsync() 之后无法访问已处置的对象
Cannot access a disposed object after HttpClient.SendAsync()
我们有一个 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>();
}
如果在不使用 var mediator= scope.ServiceProvider.GetRequiredService<IMediator>();
的情况下尝试在范围内使用 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>();
...
}
当单例服务尝试使用作用域服务时,经常会出现此类异常。 可能相关。在任何情况下,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>();
...
}
我们有一个 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>();
}
如果在不使用 var mediator= scope.ServiceProvider.GetRequiredService<IMediator>();
的情况下尝试在范围内使用 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>();
...
}
当单例服务尝试使用作用域服务时,经常会出现此类异常。 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>();
...
}