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

.NET 6中Singleton服务调用SignalR Hub方法无响应求助

问题:Singleton定时器服务调用SignalR Hub方法无响应

问题背景

基于.NET 6的Web应用中,通过控制器注入IHubContext<MyRefreshHub, IMyRefreshHubClient>调用RefreshAll方法时,页面能正常接收SignalR消息并触发AJAX刷新,但在Singleton定时器服务中调用相同方法时,虽然代码能命中断点,但服务器未发送消息,页面无响应,且日志无相关输出(控制器调用时日志正常)。

控制器正常代码

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace TestProject;

public class MyController : Controller
{
   public MyController(IHubContext<MyRefreshHub, IMyRefreshHubClient> hub)
   {
      _hub = hub;
   }

   readonly IHubContext<MyRefreshHub, IMyRefreshHubClient> _hub;

   [HttpGet]
   public IActionResult Trigger()
   {
      _hub.Clients.All.RefreshAll();
      return Ok();
   }
}

有问题的Singleton定时器服务代码

using Microsoft.AspNetCore.SignalR;

namespace TestProject;

public class TimerService
{
   public TimerService(IServiceProvider serviceProvider)
   {
      _serviceProvider = serviceProvider;
      _timer = new(30 * 100);
      _timer.Elapsed += async (_, _) => await _SendRefresh();
   }

   readonly IServiceProvider _serviceProvider;
   readonly System.Timers.Timer _timer;
  
   async Task _SendRefresh()
   {
      using var scope = _serviceProvider.CreateScope();
      var hub = scope.ServiceProvider.GetRequiredService<IHubContext<MyRefreshHub, IMyRefreshHubClient>>();
      await hub.Clients.All.RefreshAll();
   }
}

相关DI与Hub定义

DI配置:

builder.Services.AddSignalR();

services.AddScoped<MyController>();
services.AddSingleton<TimerService>();

// 立即启动定时器服务
serviceProvider.GetRequiredService<TimerService>();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages().AddNewtonsoftJson();

app.MapHub<MyRefreshHub>("/websockets/refresh_hub");

app.Run();

Hub定义:

using Microsoft.AspNetCore.SignalR;

namespace TestProject;

public interface IMyRefreshHubClient
{
    Task RefreshAll();
}

public class MyRefreshHub : Hub<IMyRefreshHubClient>
{
    public async Task RefreshAll() =>
        await Clients.All.RefreshAll();
}

排查方向

  • 定时器未启动:原代码中未调用_timer.Start(),定时器根本不会触发事件
  • IHubContext获取方式错误:IHubContext本身是Singleton生命周期,无需创建作用域获取,多余的作用域可能导致获取到无效实例
  • 异步回调异常静默失败:Elapsed事件使用async void回调,异常会直接抛到ThreadPool,无日志输出且无法察觉
  • 线程上下文丢失:ThreadPool线程触发的回调可能缺少SignalR需要的上下文(但IHubContext设计为线程安全,此问题概率较低)

解决办法

1. 直接注入IHubContext到Singleton服务

IHubContext是线程安全的Singleton服务,可直接注入到Singleton的TimerService,无需通过IServiceProvider创建作用域:

using Microsoft.AspNetCore.SignalR;

namespace TestProject;

public class TimerService
{
    private readonly IHubContext<MyRefreshHub, IMyRefreshHubClient> _hub;
    private readonly System.Timers.Timer _timer;

    public TimerService(IHubContext<MyRefreshHub, IMyRefreshHubClient> hub)
    {
        _hub = hub;
        // 修正定时器时间:30秒应为30*1000毫秒
        _timer = new System.Timers.Timer(30 * 1000);
        // 包装异步回调,捕获异常避免静默失败
        _timer.Elapsed += async (_, _) =>
        {
            try
            {
                await _SendRefresh();
            }
            catch (Exception ex)
            {
                // 添加日志记录异常
                Console.WriteLine($"SignalR发送失败:{ex.Message}\n{ex.StackTrace}");
            }
        };
        // 启动定时器
        _timer.Start();
    }

    async Task _SendRefresh()
    {
        await _hub.Clients.All.RefreshAll();
    }
}

2. 修正DI启动逻辑

确保定时器服务在ServiceProvider构建完成后启动:

var app = builder.Build();
// 获取构建完成的ServiceProvider
var serviceProvider = app.Services;
// 启动定时器服务
serviceProvider.GetRequiredService<TimerService>();

// 后续中间件配置
app.MapHub<MyRefreshHub>("/websockets/refresh_hub");
app.Run();

3. 验证定时器配置

  • 确认定时器间隔设置正确(原代码30*100是3秒,若要30秒需改为30*1000)
  • 确保_timer.AutoReset为true(默认是true,若改为false则只触发一次)

关键注意事项

  • IHubContext是Singleton,直接注入到Singleton服务完全安全,无需额外作用域
  • 异步回调必须添加try-catch,避免async void导致的静默异常
  • 定时器必须调用Start()方法才会开始触发事件

内容的提问来源于stack exchange,提问作者Nolan Blew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 01:35:27