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

