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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.16 23:54:01