调用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
相关产品推荐
相关产品推荐

