.NET 9 Blazor Server应用SignalR连接无法自动关闭问题
客户端(JavaScript)实现
直接访问Blazor内部的SignalR连接实例并调用终止方法,是最直接的客户端解决方案:
核心逻辑:
- 先调用
Blazor.disconnect()销毁Blazor回路,释放服务器资源 - 通过
Blazor._internal.connection获取底层SignalR连接,调用stop()方法彻底断开WebSocket连接
- 先调用
完整代码示例(结合非活跃检测):
// 配置非活跃超时时间(30分钟,可自定义) const INACTIVITY_TIMEOUT = 30 * 60 * 1000; let inactivityTimer; // 监听用户活动事件,重置定时器 ['mousemove', 'keydown', 'click', 'scroll'].forEach(event => { window.addEventListener(event, resetTimer); }); function resetTimer() { clearTimeout(inactivityTimer); inactivityTimer = setTimeout(handleInactivity, INACTIVITY_TIMEOUT); } function handleInactivity() { if (typeof Blazor === 'undefined') return; // 销毁Blazor回路 Blazor.disconnect(); // 断开底层SignalR连接 if (Blazor._internal?.connection) { Blazor._internal.connection.stop() .then(() => { // 显示注销提示 alert('您已长时间未操作,已自动注销'); // 可选:跳转到登录页面 window.location.href = '/login'; }) .catch(err => console.error('断开SignalR连接失败:', err)); } } // 页面加载时启动定时器 resetTimer();
注意:Blazor._internal是内部API,.NET 9中稳定可用,但后续版本可能存在变更风险。
服务器端(C#)实现
如果担心客户端内部API的兼容性,可采用服务器端方案,通过Circuit跟踪和SignalR配置实现彻底断开:
方案1:配置SignalR超时
在Program.cs中调整SignalR的超时参数,确保回路销毁后连接自动关闭:
builder.Services.AddSignalR(options => { // 设置客户端超时时间,回路销毁后触发断开 options.ClientTimeoutInterval = TimeSpan.FromSeconds(5); // 缩短心跳间隔,加速超时检测 options.KeepAliveInterval = TimeSpan.FromSeconds(2); });
方案2:自定义Circuit Handler跟踪非活跃用户
通过自定义CircuitHandler主动检测并断开非活跃回路:
using Microsoft.AspNetCore.Components.Server.Circuits; using System.Collections.Concurrent; public class InactivityCircuitHandler : CircuitHandler { private readonly ConcurrentDictionary<string, DateTime> _lastActivityRecords = new(); private readonly ILogger<InactivityCircuitHandler> _logger; private Timer? _cleanupTimer; private readonly TimeSpan _inactivityThreshold = TimeSpan.FromMinutes(30); public InactivityCircuitHandler(ILogger<InactivityCircuitHandler> logger) { _logger = logger; // 每分钟检查一次非活跃回路 _cleanupTimer = new Timer(CleanupInactiveCircuits, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken) { _lastActivityRecords[circuit.Id] = DateTime.UtcNow; return base.OnConnectionUpAsync(circuit, cancellationToken); } public override Task OnCircuitActivityAsync(Circuit circuit, DateTime lastActivity, CancellationToken cancellationToken) { _lastActivityRecords[circuit.Id] = DateTime.UtcNow; return base.OnCircuitActivityAsync(circuit, lastActivity, cancellationToken); } private void CleanupInactiveCircuits(object? state) { var now = DateTime.UtcNow; var inactiveCircuitIds = _lastActivityRecords .Where(kv => now - kv.Value > _inactivityThreshold) .Select(kv => kv.Key) .ToList(); foreach (var circuitId in inactiveCircuitIds) { if (CircuitManager.TryGetCircuit(circuitId, out var circuit)) { _logger.LogInformation("断开非活跃回路: {CircuitId}", circuitId); // 销毁回路,触发SignalR连接超时 circuit.DisposeAsync().Wait(); } _lastActivityRecords.TryRemove(circuitId, out _); } } public override void Dispose() { _cleanupTimer?.Dispose(); base.Dispose(); } } // 在Program.cs中注册 builder.Services.AddSingleton<CircuitHandler, InactivityCircuitHandler>();
说明:服务器端方案无需依赖客户端内部API,兼容性更强,但需要在服务器端维护用户活动状态。
内容的提问来源于stack exchange,提问作者toby
相关产品推荐
相关产品推荐

