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

.NET 8 SignalR SendAsync长时间连接后挂起,前端仍可接收消息

.NET 8 SignalR SendAsync 长时间连接后挂起问题排查与修复

可能的原因

  • 连接目标调用错误:你代码中使用Clients.User(<connection id>)是错误的——User方法是根据用户标识(如Claims中的用户ID)筛选连接,而非直接使用连接ID。误用会导致服务端无法定位到正确的连接,长时间后出现挂起。
  • 长轮询连接会话不同步:LongPolling模式下客户端会定期发起新请求,若服务端未及时更新连接映射,使用旧连接ID发送消息时,服务端会持续等待已失效连接的响应,导致SendAsync挂起。此时前端仍能接收消息,说明新连接已建立,但服务端的连接ID映射未同步。
  • 消息队列阻塞:长时间运行后,针对无效连接的消息堆积,SendAsync等待消息被处理,但旧连接无法确认消息,导致队列阻塞。
  • 超时配置不合理:默认的SignalR长轮询超时设置可能过长,导致连接处于半开状态,服务端误判连接存活,持续等待客户端响应。

诊断方法(不使用取消令牌)

  • 修正调用方式并验证:先将Clients.User(<connection id>)改为Clients.Client(<connection id>),观察问题是否消失——这是最可能的直接修复点。
  • 监控活跃连接状态:在Hub的OnConnectedAsync/OnDisconnectedAsync中维护连接映射表,SendAsync前后打印当前活跃连接ID,确认使用的连接ID是否仍在活跃列表:
    private static readonly HashSet<string> _activeConnections = new HashSet<string>();
    
    public override async Task OnConnectedAsync()
    {
        lock (_activeConnections)
        {
            _activeConnections.Add(Context.ConnectionId);
        }
        await base.OnConnectedAsync();
    }
    
    public override async Task OnDisconnectedAsync(Exception exception)
    {
        lock (_activeConnections)
        {
            _activeConnections.Remove(Context.ConnectionId);
        }
        await base.OnDisconnectedAsync(exception);
    }
    
    // 在SendAsync前检查
    if (!_activeConnections.Contains(targetConnectionId))
    {
        // 记录日志:目标连接已失效
        return;
    }
    await hub.Clients.Client(targetConnectionId).SendAsync(message, data);
    
  • 启用SignalR详细日志:在服务端appsettings.json中配置日志级别,查看SendAsync执行时的细节:
    "Logging": {
      "LogLevel": {
        "Microsoft.AspNetCore.SignalR": "Debug",
        "Microsoft.AspNetCore.Http.Connections": "Debug"
      }
    }
    
    日志会显示消息发送的目标连接、是否等待响应、连接状态等信息,帮助定位阻塞点。
  • 前端连接状态日志:在前端添加连接事件监听,记录重连、断开事件及新连接ID,对比服务端使用的ID是否匹配:
    this.connectionWebService.onreconnecting((error) => {
        console.log(`Reconnecting: ${error}`);
    });
    
    this.connectionWebService.onreconnected((connectionId) => {
        console.log(`Reconnected with new ID: ${connectionId}`);
        // 可将新连接ID同步到服务端
    });
    
    this.connectionWebService.onclose((error) => {
        console.log(`Connection closed: ${error}`);
    });
    

修复方案

  • 使用正确的客户端调用方式:始终用Clients.Client(connectionId)向指定连接ID发送消息,Clients.User(userId)用于向某用户的所有连接发送消息。
  • 维护准确的连接映射:通过Hub的连接事件维护用户与活跃连接ID的映射,确保发送时使用的是当前有效的连接ID(参考诊断部分的映射表代码)。
  • 调整超时配置:在服务端和前端同步调整SignalR超时参数,避免半开连接:
    服务端(Program.cs):
    builder.Services.AddSignalR(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromMinutes(5); // 客户端无响应超时
        options.KeepAliveInterval = TimeSpan.FromMinutes(2); // 服务端发送心跳间隔
    });
    
    前端:
    this.connectionWebService.serverTimeoutInMilliseconds = 300000; // 5分钟
    this.connectionWebService.keepAliveIntervalInMilliseconds = 120000; // 2分钟
    
  • 定期清理无效连接:通过定时任务或连接事件清理映射表中的无效连接ID,避免向已断开的连接发送消息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:57:07