添加Timer后SignalR Hub出现ObjectDisposedException异常求助
问题分析与解决方法
问题根源
你遇到的ObjectDisposedException本质原因是:
- SignalR Hub实例是短暂生命周期的,默认每个客户端连接/请求都会创建新的Hub实例,DI容器注入的
MyContext与Hub同生命周期,当Hub被销毁时,MyContext也会被自动释放。 - 你在Hub构造函数中创建的
Timer是后台线程,生命周期独立于Hub,即使Hub实例被销毁,Timer仍会持续触发回调。当回调执行SendProgressUpdate时,_dbContext已经被DI容器释放,因此抛出异常。 - 你添加的
Dispose方法无法彻底解决问题:一是SignalR的Hub销毁时机可能滞后于Timer回调的触发;二是即使标记了_timerDisposed,已经进入队列的回调仍会执行到访问DbContext的代码。
最优解决方案:将定时逻辑移至后台服务
SignalR Hub的设计目的是处理客户端连接,不适合承载长期运行的定时任务。正确的做法是使用**后台服务(IHostedService)**管理Timer,通过IHubContext向客户端推送消息。
1. 创建后台服务类
public class ProgressUpdateService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly IHubContext<ProgressHub> _hubContext; private Timer _timer; public ProgressUpdateService(IServiceScopeFactory scopeFactory, IHubContext<ProgressHub> hubContext) { _scopeFactory = scopeFactory; _hubContext = hubContext; } protected override Task ExecuteAsync(CancellationToken stoppingToken) { // 初始化Timer,每30秒执行一次 _timer = new Timer(async _ => await SendProgressUpdate(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); return Task.CompletedTask; } private async Task SendProgressUpdate() { // 每次调用创建新的服务范围,确保DbContext是新鲜且未被释放的实例 using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<MyContext>(); // 执行数据查询逻辑 var totalCompleted = dbContext.xxx; // ... 其他业务代码 // 通过HubContext向所有客户端推送更新 await _hubContext.Clients.All.SendAsync("ReceiveProgressUpdate", totalCompleted); } public override void Dispose() { // 停止并释放Timer _timer?.Change(Timeout.Infinite, Timeout.Infinite); _timer?.Dispose(); base.Dispose(); } }
2. 注册后台服务
在Program.cs中添加后台服务的注册:
builder.Services.AddHostedService<ProgressUpdateService>();
3. 简化ProgressHub
移除Hub中的Timer和DbContext依赖,仅保留连接相关逻辑(如果有的话):
public class ProgressHub : Hub { // 无需额外字段,仅处理客户端连接/断开等逻辑(如果需要) }
备选方案:在Hub中使用Scope获取DbContext(不推荐)
如果一定要在Hub中保留Timer,需要避免直接使用构造注入的DbContext,而是每次回调时通过IServiceScopeFactory创建新的DbContext实例:
public class ProgressHub : Hub, IDisposable { private readonly IServiceScopeFactory _scopeFactory; private Timer _timer; private bool _disposed = false; public ProgressHub(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; _timer = new Timer(async _ => await SendProgressUpdate(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); } private async Task SendProgressUpdate() { if (_disposed) return; // 创建新的服务范围,获取新鲜的DbContext实例 using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<MyContext>(); var totalCompleted = dbContext.xxx; // ... 其他业务代码 await Clients.All.SendAsync("ReceiveProgressUpdate", totalCompleted); } public override void Dispose() { _disposed = true; _timer?.Change(Timeout.Infinite, Timeout.Infinite); _timer?.Dispose(); base.Dispose(); } }
注意:此方案存在隐患——每个Hub实例都会创建一个Timer,若有多个客户端连接,会导致多个Timer同时运行,重复推送消息,因此不推荐使用。
内容的提问来源于stack exchange,提问作者Steve
相关产品推荐
相关产品推荐

