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

.NET 6关闭标签/浏览器时Dispose延迟或不触发 页面独占锁无法释放

Blazor单页应用独占锁Dispose不触发问题解决方案

关于Dispose触发延迟的说明

这个现象不是.NET 6的设计特性,核心是资源清理逻辑的触发依赖浏览器与服务端的通信状态:

  • 若你使用的是Blazor Server场景,组件Dispose依赖SignalR连接的正常断开通知。默认服务器端连接断开检测超时为300秒(5分钟),如果浏览器直接关闭、进程被杀,没有主动发送WebSocket关闭帧,服务器需要等待超时后才会清理对应连接的组件资源,触发Dispose。火狐浏览器关闭时默认不会主动发送WebSocket关闭帧的行为是已知的浏览器差异,会导致服务器完全收不到断开通知,只能等待最长连接回收超时才会清理,极端情况如果服务端进程崩溃未触发回收逻辑,就会出现Dispose永不触发的问题。
  • 若你使用的是Blazor WASM场景,页面卸载时的beforeunload/unload事件本身存在浏览器兼容性限制,部分场景下浏览器会直接终止页面进程,不会执行事件内的逻辑,导致托管的.NET运行时没有机会调用组件的Dispose方法。

另外你原本依赖Dispose释放独占锁的逻辑本身就存在设计漏洞,无法覆盖用户设备断网、进程异常崩溃、页面强制结束等场景,就算Dispose完全正常触发,也存在锁永久占用的风险。

可靠的独占锁实现方案

1. 新增超时心跳机制(核心必备)

给独占锁增加过期时间,要求活跃用户定时发送心跳更新锁的过期时间,超过指定时长没有收到心跳则自动释放锁,从根本上解决锁永久占用的问题。
示例实现代码:

// Singleton对象AppData的修改实现
public class AppData
{
    public string? InUseByUser { get; private set; }
    public DateTime LockExpireAt { get; private set; }
    private readonly object _lockObj = new();

    // 抢锁接口,用户进入页面时调用
    public bool TryAcquireLock(string userId)
    {
        lock (_lockObj)
        {
            if (InUseByUser == null || LockExpireAt < DateTime.Now)
            {
                InUseByUser = userId;
                LockExpireAt = DateTime.Now.AddSeconds(30);
                return true;
            }
            // 锁的持有者本人可以直接访问
            return InUseByUser == userId;
        }
    }

    // 心跳续约接口,前端定时调用
    public void RenewLock(string userId)
    {
        lock (_lockObj)
        {
            if (InUseByUser == userId)
            {
                LockExpireAt = DateTime.Now.AddSeconds(30);
            }
        }
    }

    // 主动释放接口,正常退出时调用
    public void ReleaseLock(string userId)
    {
        lock (_lockObj)
        {
            if (InUseByUser == userId)
            {
                InUseByUser = null;
            }
        }
    }
}

前端页面每隔15秒调用一次RenewLock接口即可,就算Dispose完全不触发,最多30秒锁也会自动释放。

2. 补充前端页面卸载主动通知

前端监听beforeunload事件,正常关闭页面时主动调用后端的ReleaseLock接口,作为心跳机制的补充,加快正常场景下的锁释放速度。注意请求需要开启keepalive属性避免被浏览器中断:

window.addEventListener('beforeunload', () => {
    fetch('/api/releaselock', {
        method: 'POST',
        keepalive: true,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ userId: 当前登录用户ID })
    });
});

3. 调整Blazor Server连接超时参数(可选)

如果是Blazor Server场景,可以适当调低SignalR的断开超时参数,降低正常关闭场景下Dispose的触发延迟:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        // 客户端超时时间,默认300秒
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
        // 服务端心跳间隔,默认15秒
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    });

注意该参数不要设置过低,否则网络抖动容易导致正常用户被误踢下线。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 05:45:08