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

无法通过DI注入HubContext时,如何从普通类调用SignalR Hub?

在普通类中使用SignalR的IHubContext

核心问题解答

可以从任意普通类中调用SignalR Hub发送消息,不需要强制将类注册为DI服务,但最合理的方式是通过依赖注入获取IHubContext<THub>实例——注意不要手动创建Hub实例,Hub是由SignalR框架管理的,手动实例无法正常处理客户端连接逻辑。

你的场景分析与实现

你的场景是合理的,本质是模拟后台异步逻辑触发全局消息推送的需求,类似事件驱动通知、异步任务回调的场景。下面是具体实现方案,同时解决静态列表的潜在问题:

1. 定义TestHub

Hub本身通过DI注入IHubContext<TestHub>,在客户端连接时创建Whatever实例并传递上下文:

public class TestHub : Hub
{
    private readonly IHubContext<TestHub> _hubContext;

    public TestHub(IHubContext<TestHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public override async Task OnConnectedAsync()
    {
        // 为当前连接创建Whatever实例,传入IHubContext和连接ID
        var whatever = new Whatever(_hubContext, Context.ConnectionId);
        
        // 线程安全地添加到静态列表(多客户端并发连接时必须加锁)
        lock (Whatever.ActiveInstancesLock)
        {
            Whatever.ActiveInstances.Add(whatever);
        }
        
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // 客户端断开时清理对应Whatever实例,避免内存泄漏
        lock (Whatever.ActiveInstancesLock)
        {
            var instanceToRemove = Whatever.ActiveInstances.FirstOrDefault(w => w.ConnectionId == Context.ConnectionId);
            if (instanceToRemove != null)
            {
                Whatever.ActiveInstances.Remove(instanceToRemove);
            }
        }
        
        await base.OnDisconnectedAsync(exception);
    }
}

2. 定义Whatever类

通过构造函数接收IHubContext<TestHub>,执行随机延迟后发送消息:

public class Whatever
{
    public static List<Whatever> ActiveInstances { get; } = new List<Whatever>();
    public static object ActiveInstancesLock { get; } = new object();
    public string ConnectionId { get; }
    private readonly IHubContext<TestHub> _hubContext;

    public Whatever(IHubContext<TestHub> hubContext, string connectionId)
    {
        _hubContext = hubContext;
        ConnectionId = connectionId;
        // 启动异步任务,不阻塞Hub连接流程
        _ = ExecuteDelayedMessage();
    }

    private async Task ExecuteDelayedMessage()
    {
        try
        {
            var randomDelay = new Random().Next(1000, 10000); // 1-10秒随机延迟
            await Task.Delay(randomDelay);

            // 向所有客户端发送消息
            await _hubContext.Clients.All.SendAsync("ReceiveGlobalMessage", 
                $"来自连接 {ConnectionId} 的延迟消息,延迟了 {randomDelay}ms");
        }
        catch (Exception ex)
        {
            // 处理异步任务异常,避免未处理异常导致进程崩溃
            Console.WriteLine($"Whatever执行异常: {ex.Message}");
        }
    }
}

场景优化说明

你的原实现存在两个关键问题,上面的代码已修复:

  • 线程安全问题:静态列表是非线程安全的,多客户端同时连接时必须用锁保护Add/Remove操作,否则会出现集合修改异常。
  • 内存泄漏风险:客户端断开后,如果不清理Whatever实例,这些对象会一直留在静态列表中,长期运行会导致内存占用持续升高。通过在OnDisconnectedAsync中移除对应实例解决。

另外,不建议使用服务定位器模式(比如静态保存IServiceProvider然后在Whatever中获取IHubContext),这种方式会破坏DI的解耦原则,增加代码测试难度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 09:55:13