ASP.NET Core非控制器类使用SignalR时Clients.All为空如何解决
问题根源
你在ConfigureServices阶段手动调用services.BuildServiceProvider()生成了一个局部服务容器,这个容器和ASP.NET Core后续运行时使用的根服务容器是完全独立的两个实例:
- 你从局部容器解析出来的
IGeneralStockMarketHandler持有的IHubContext,绑定的是局部容器的SignalR状态 - 后续客户端实际连接的是根容器内的SignalR实例,两个实例的连接状态完全隔离,所以你提前初始化的Handler永远看不到客户端连接。
手动调用BuildServiceProvider是ASP.NET Core的常见误区,会导致服务实例重复、单例状态分裂、生命周期异常等问题。
解决方案
删掉ConfigureServices里手动构建容器、解析Handler执行初始化的三行代码,改用官方推荐的启动任务方案执行初始化逻辑,保证在根容器完全初始化完成后再执行你的订阅逻辑。
方案1:用托管服务IHostedService执行初始化(推荐)
步骤1:实现托管服务类
如果你的IGeneralStockMarketHandler是单例/瞬态注册:
public class StockMarketInitHostedService : IHostedService { private readonly IGeneralStockMarketHandler _stockHandler; public StockMarketInitHostedService(IGeneralStockMarketHandler stockHandler) { _stockHandler = stockHandler; } public Task StartAsync(CancellationToken cancellationToken) { // 执行你的启动订阅逻辑,异步方法直接加await即可 _stockHandler.StartToSubscribeAllStocksFromDb(); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { // 应用停止时可在这里添加取消订阅、释放资源的逻辑 return Task.CompletedTask; } }
如果你的IGeneralStockMarketHandler是Scoped注册,需要手动创建作用域解析:
public class StockMarketInitHostedService : IHostedService { private readonly IServiceScopeFactory _scopeFactory; public StockMarketInitHostedService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public Task StartAsync(CancellationToken cancellationToken) { using var scope = _scopeFactory.CreateScope(); var stockHandler = scope.ServiceProvider.GetRequiredService<IGeneralStockMarketHandler>(); stockHandler.StartToSubscribeAllStocksFromDb(); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }
步骤2:注册托管服务
在ConfigureServices里添加一行注册即可:
services.AddHostedService<StockMarketInitHostedService>();
方案2:在Configure中用IHostApplicationLifetime执行初始化
你也可以直接在管道配置阶段,监听应用启动完成事件后执行初始化:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime, IServiceScopeFactory scopeFactory) { // 监听应用完全启动事件 appLifetime.ApplicationStarted.Register(() => { using var scope = scopeFactory.CreateScope(); var stockHandler = scope.ServiceProvider.GetRequiredService<IGeneralStockMarketHandler>(); stockHandler.StartToSubscribeAllStocksFromDb(); }); // 保留你原来的管道配置不变 app.UseCors("CorsPolicy"); app.UseEndpoints(endpoints => { endpoints.MapHub<NotificationHub>("/notificationhub"); endpoints.MapControllers(); }); }
补充说明
你之前对IHubContext的生命周期理解有误:IHubContext<THub>默认是单例服务,只要从根容器解析,所有位置拿到的都是同一个实例,控制器和普通类里的实例状态完全一致,改完后你就会发现Clients.All能正常获取到所有连接的客户端。
内容的提问来源于stack exchange,提问作者boss
相关产品推荐
相关产品推荐

