.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
相关产品推荐
相关产品推荐

