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

调用InitRedis时触发System.ObjectDisposedException异常求助

问题分析

异常根源

你遇到的System.ObjectDisposedException本质是生命周期不匹配导致的上下文提前释放:

  • IHostedService(你的Alarm类)是单例生命周期服务,而AlarmHandler是Scoped服务,IoTDbContext默认也是Scoped生命周期。
  • 若直接将IoTDbContext注入AlarmHandler,当你在Alarm的构造函数中通过using创建服务范围获取AlarmHandler后,服务范围会在using块结束时被释放,依附于该范围的Scoped上下文也会被回收。后续调用InitRedis时,复用的AlarmHandler持有的上下文已经是已释放状态,就会抛出异常。
  • 之前直接new AlarmHandler能运行,是因为手动实例化的对象脱离了DI容器的生命周期管理,上下文不会被容器自动释放,但这是不规范的写法,会导致数据库连接无法被正确回收,长期运行会引发连接池耗尽等问题。

IDbContextFactory解决问题的原因

IDbContextFactory是专门为单例服务中安全使用Scoped上下文设计的工具:

  • 它创建的上下文实例不受服务范围的约束,每次调用CreateDbContext()都会生成一个独立的上下文实例。
  • 你可以通过using语句手动控制上下文的生命周期,用完即释放,不会出现复用已释放实例的情况,同时也能避免资源泄漏。
彻底解决的规范实现

1. Program.cs 注册调整

确保IoTDbContext和IDbContextFactory正确注册(ASP.NET Core 6+默认会自动注册,但手动注册更清晰):

builder.Services.AddDbContextFactory<IoTDbContext>(options =>
{
    // 替换为你的数据库配置,比如SQL Server连接字符串
    options.UseSqlServer(builder.Configuration.GetConnectionString("IoTConnection"));
});

// 注册HostedService和Scoped服务
builder.Services.AddHostedService<Alarm>();
builder.Services.AddScoped<AlarmHandler>();

2. Alarm类(IHostedService)的正确写法

单例服务不能持有Scoped服务实例,必须在每次需要调用时创建新的服务范围:

public class Alarm : IHostedService
{
    private readonly IServiceProvider _serviceProvider;

    public Alarm(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // 每次调用都创建独立的服务范围,确保Scoped服务生命周期正确
        using var scope = _serviceProvider.CreateScope();
        var alarmHandler = scope.ServiceProvider.GetRequiredService<AlarmHandler>();
        await alarmHandler.InitRedis("setting");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

3. AlarmHandler类的规范实现

通过IDbContextFactory创建上下文,确保每次使用都是独立实例:

public class AlarmHandler
{
    private readonly IDbContextFactory<IoTDbContext> _dbContextFactory;

    public AlarmHandler(IDbContextFactory<IoTDbContext> dbContextFactory)
    {
        _dbContextFactory = dbContextFactory;
    }

    public async Task InitRedis(string settingKey)
    {
        // 使用using确保上下文用完自动释放,避免资源泄漏
        using var context = _dbContextFactory.CreateDbContext();
        // 执行数据库查询逻辑
        var settings = await context.Settings.Where(s => s.Key == settingKey).ToListAsync();
        // 后续写入Redis的逻辑...
    }
}
关键注意事项
  • 禁止在单例服务中持有Scoped服务的实例,这会破坏DI的生命周期管理规则,必然导致上下文释放或资源泄漏问题。
  • 永远不要手动new需要DI注入依赖的服务实例,绕过容器会导致依赖无法被正确管理,引发各种隐性问题。
  • IDbContextFactory是单例服务访问Scoped上下文的标准方案,它既保证了上下文的正确释放,又避免了生命周期冲突。

内容的提问来源于stack exchange,提问作者slim shady

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:29:55