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

.NET 9 Blazor Server应用SignalR连接无法自动关闭问题

客户端(JavaScript)实现

直接访问Blazor内部的SignalR连接实例并调用终止方法,是最直接的客户端解决方案:

  1. 核心逻辑:

    • 先调用Blazor.disconnect()销毁Blazor回路,释放服务器资源
    • 通过Blazor._internal.connection获取底层SignalR连接,调用stop()方法彻底断开WebSocket连接
  2. 完整代码示例(结合非活跃检测):

// 配置非活跃超时时间(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 08:27:34