c# - 在 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>();
}
如果尝试在范围内使用 IMediator 而不使用 using 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>();
...
}
解决方案
当单例服务尝试使用作用域服务时,经常会发生此类异常。这个关于 MediatR 和范围的问题可能是相关的。在任何情况下,HttpClientFactory 都会定期回收 HttpClient 实例(特别是处理程序实例),这意味着如果HttpMagicService
将 HttpClient 实例缓存足够长的时间,它将在某个时候被释放。
在任何一种情况下,您都可以通过将 IServiceProvider 注入 HttpMagicService 并显式创建范围来使用 HttpClient。检查在后台任务中使用范围服务。
您可以注入 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>();
...
}
推荐阅读
- excel - 在 Excel 的散点图中添加 ± 10 % 的线
- javascript - 类不会在滚动时切换
- python-3.x - 在大熊猫的帮助下,在 matplotlib 或 seaborn 中绘制折线图并突出显示最小点
- c# - 在 winform 中持续使用 API (C#)
- docker - 由未知权威签署的 Docker 证书
- javascript - 缩短长度字符串忽略非空格字符,如阿拉伯语变音符号字符而不切割单词
- python - 在 NumPy 中向量化矩阵乘法
- azure - Azure 虚拟机 ARM 模板 CopyIndex 排除 avSet
- laravel-authentication - Laravel lighthouse 4.16 在不使用已弃用的@middleware 的情况下获取当前用户
- r - 计算每个值的行数并将它们绘制在 R 中