在 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>();
         ...
        }