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

添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:10:57