You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReqNroll集成测试中HangFire实例销毁异常问题求助

问题:连续运行ReqNroll集成测试时HangFire InMemory调度器已销毁报错

问题现象

单个测试运行正常,连续执行所有测试时仅第一个成功,其余均触发内部服务器错误,抛出ObjectDisposedException,提示访问已销毁的HangFire调度器对象。

错误详情

[2024-08-30 09:35:55] fail: TestProject.Api.Filters.GlobalApiExceptionFilter[0]
      An exception occurred while processing the request.
      Hangfire.BackgroundJobClientException: Background job creation failed. See inner exception for details.
       ---> System.ObjectDisposedException: Cannot access a disposed object.
      Object name: 'Hangfire.InMemory.State.Dispatcher`1[[System.UInt64, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken={}]]'.
         at Hangfire.InMemory.State.Dispatcher`1.ThrowObjectDisposedException() in /_/src/Hangfire.InMemory/State/Dispatcher.cs:line 170
         at Hangfire.InMemory.State.Dispatcher`1.QueryWriteAndWait(ICommand`2 query) in /_/src/Hangfire.InMemory/State/Dispatcher.cs:line 68
         at Hangfire.InMemory.State.DispatcherBase`1.QueryWriteAndWait[T](ICommand`2 query) in /_/src/Hangfire.InMemory/State/DispatcherBase.cs:line 168
         at Hangfire.InMemory.InMemoryConnection`1.CreateExpiredJob(Job job, IDictionary`2 parameters, DateTime createdAt, TimeSpan expireIn) in /_/src/Hangfire.InMemory/InMemoryConnection.cs:line 112
         at Hangfire.Client.CoreBackgroundJobFactory.<>c__DisplayClass15_0.<CreateBackgroundJobTwoSteps>b__0(Int32 _)
         at Hangfire.Client.CoreBackgroundJobFactory.RetryOnException[T](Int32& attemptsLeft, Func`2 action)
      --- End of stack trace from previous location ---

测试环境与代码

使用ReqNroll + NUnit,基于WebApplicationFactory配置外部服务桩、内存数据库和HangFire InMemory实例。

Setup与CleanUp代码

[BeforeScenario]
public void Setup()
{
    _httpClient = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services
                .ConfigureExternalConnectionStubs()
                .ConfigureLocalDb()
                .ConfigureHangfire();
        });
    }).CreateClient();
    _potentialThreatRequests = [];
    _statusCodes = [];
    _httpContents = [];
    _cachedThreatResponses = [];
    _cachedProblemDetails = [];
}
[AfterScenario]
public void CleanUp()
{
    _httpClient.Dispose();
}

已在ConfigureLocalDb和ConfigureHangfire中移除旧服务并重新注册内存版HangFire,但问题仍存在,且仅在连续执行文件上传类测试步骤时触发。已尝试销毁HttpClient、重新初始化JobStorage,无效。使用版本:Hangfire 1.8.5、Hangfire.InMemory 0.10.1、ReqNroll 2.0.3。

原因分析

  1. HangFire InMemory实例生命周期冲突:WebApplicationFactory默认复用宿主实例,第一次测试结束后HangFire调度器被销毁,但后续测试复用了包含已销毁对象的服务容器,导致访问已释放资源。
  2. 测试隔离不彻底:当前Setup仅重新配置服务,未彻底重置宿主环境,前一次测试的HangFire状态(含已销毁调度器)残留。
  3. HttpClient与宿主绑定关系未切断:虽每次测试创建新HttpClient,但WithWebHostBuilder未强制创建全新宿主,宿主内的HangFire服务已被前一次测试的销毁流程标记为已释放。

解决方案

1. 每次测试创建全新WebApplicationFactory实例

不再复用全局_factory,每个测试的Setup中创建新工厂实例,确保服务容器和HangFire实例完全独立:

private WebApplicationFactory<Program> _factory;

[BeforeScenario]
public void Setup()
{
    _factory = new WebApplicationFactory<Program>();
    _httpClient = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services
                .ConfigureExternalConnectionStubs()
                .ConfigureLocalDb()
                .ConfigureHangfire();
        });
    }).CreateClient();
    // 其他初始化代码...
}

[AfterScenario]
public void CleanUp()
{
    _httpClient.Dispose();
    _factory.Dispose(); // 销毁工厂,彻底释放宿主资源
}

2. 彻底重置HangFire服务与状态

在ConfigureHangfire中,不仅移除旧服务,还要销毁旧JobStorage实例,强制创建全新InMemory存储:

public static IServiceCollection ConfigureHangfire(this IServiceCollection services)
{
    // 移除所有HangFire相关服务描述
    var hangfireDescriptors = services.Where(d => 
        d.ServiceType == typeof(IJobStorage) || 
        d.ServiceType == typeof(IBackgroundJobClient) ||
        d.ServiceType == typeof(IRecurringJobManager)).ToList();
    foreach (var descriptor in hangfireDescriptors)
    {
        services.Remove(descriptor);
    }

    // 销毁旧的JobStorage实例
    if (JobStorage.Current is IDisposable disposableStorage)
    {
        disposableStorage.Dispose();
    }

    // 注册全新的InMemory HangFire服务
    services.AddHangfire(config => config.UseInMemoryStorage());
    services.AddHangfireServer();

    return services;
}

3. 禁用WebApplicationFactory宿主复用

通过配置工厂选项,强制每次测试创建全新宿主:

_factory = new WebApplicationFactory<Program>()
{
    Options = new WebApplicationFactoryOptions
    {
        AllowHostReuse = false
    }
};

4. 测试后主动清理HangFire后台服务

在AfterScenario中获取并销毁HangFire后台服务器实例,确保资源完全释放:

[AfterScenario]
public void CleanUp()
{
    _httpClient.Dispose();
    
    // 停止并销毁HangFire后台服务
    var hangfireServer = _factory.Services.GetService<IHostedService>() as BackgroundJobServer;
    hangfireServer?.StopAsync(CancellationToken.None).Wait();
    hangfireServer?.Dispose();
    
    _factory.Dispose();
}

内容的提问来源于stack exchange,提问作者Jan Pul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:04:57