System.ObjectDisposedException求助:已释放EF上下文实例访问问题
解决System.ObjectDisposedException:已释放的DbContext实例访问问题
遇到System.ObjectDisposedException异常,错误信息:无法访问已释放的上下文实例。此错误的常见原因是使用依赖注入解析上下文实例后将其释放,之后又在应用程序其他地方尝试使用同一个上下文实例。
问题根源分析
JobbyContext作为EF Core的DbContext,默认是Scoped生命周期,它的生命周期与HTTP请求绑定,请求结束后会被DI容器自动释放。TimerManager的定时器回调在后台异步执行,当触发回调时,原请求已经结束,注入到控制器的_Context实例早已被释放,此时再访问_Context.message就会抛出异常。TimerManager中的Execute方法使用async void,会导致异步操作的异常无法被正确捕获和处理,属于不良实践。
解决方案
方案1:在定时器回调中创建新的DbContext实例
每次回调都生成全新的上下文实例,确保操作时上下文未被释放:
// 先在控制器中注入DbContext配置项 private readonly DbContextOptions<JobbyContext> _contextOptions; public RealTimeMessage(IHubContext<MessageHub, IMessageHubClient> _messageHub, JobbyContext C, TimerManager T, DbContextOptions<JobbyContext> contextOptions) { messageHub = _messageHub; _Context = C; _timer = T; _contextOptions = contextOptions; } // 修改SendRealTimeMessage中的回调逻辑 [HttpGet] [Route("SendMessage")] public async Task<IActionResult> SendRealTimeMessage() { if (!_timer.IsTimerStarted) await _timer.PrepareTimer(async ()=> { using var context = new JobbyContext(_contextOptions); var m = await context.message.ToListAsync(x=> x.IsRead==false); if (m.Count>0) { await messageHub.Clients.All.SendMessageToUser(m); } }); return Ok(new { Message = "Request Completed" }); }
方案2:使用IServiceScopeFactory创建Scoped上下文
通过服务作用域工厂在回调中创建独立的作用域,从中获取DbContext:
// 在控制器中注入IServiceScopeFactory private readonly IServiceScopeFactory _scopeFactory; public RealTimeMessage(IHubContext<MessageHub, IMessageHubClient> _messageHub, JobbyContext C, TimerManager T, IServiceScopeFactory scopeFactory) { messageHub = _messageHub; _Context = C; _timer = T; _scopeFactory = scopeFactory; } // 修改回调逻辑 await _timer.PrepareTimer(async ()=> { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<JobbyContext>(); var m = await context.message.ToListAsync(x=> x.IsRead==false); if (m.Count>0) { await messageHub.Clients.All.SendMessageToUser(m); } });
额外修复:将async void改为async Task
修正TimerManager中的异步方法写法,避免异常丢失:
public class TimerManager { private Timer? _timer; private AutoResetEvent? _autoResetEvent; private Func<Task>? _action; public DateTime TimerStarted { get; set; } public bool IsTimerStarted { get; set; } public void PrepareTimer(Func<Task> action) { _action = action; _autoResetEvent = new AutoResetEvent(false); _timer = new Timer(async (state) => await Execute(state), _autoResetEvent, 1000, 3000); TimerStarted = DateTime.Now; IsTimerStarted = true; } public async Task Execute(object? stateInfo) { await _action(); if ((DateTime.Now - TimerStarted).TotalSeconds > 10000) { IsTimerStarted = false; _timer?.Dispose(); } } }
内容的提问来源于stack exchange,提问作者Marwen Saad
相关产品推荐
相关产品推荐

