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

关闭浏览器时OnDisconnectedAsync未触发,行锁无法释放求助

解决SignalR行锁定在浏览器关闭/刷新时无法自动释放的问题

当前实现的核心问题在于:OnDisconnectedAsync方法仅尝试移除连接建立时URL参数中指定的row-id对应的锁,但实际场景中用户可能在单个会话内锁定多行,且连接建立时不一定传递该参数,导致浏览器关闭或刷新时,所有该用户锁定的行无法被自动释放。

解决方案

通过维护连接ID到锁定行ID的反向映射,在连接断开时遍历并释放该连接所有锁定的行,修改后的ItemAccessHub代码如下:

public class ItemAccessHub : Hub
{
    private static readonly ConcurrentDictionary<string, (string ConnectionId, string Module, string Username)> RowLocks = new();
    // 新增反向映射:记录每个连接锁定的所有行ID
    private static readonly ConcurrentDictionary<string, HashSet<string>> ConnectionToRowLocks = new();

    public override Task OnConnectedAsync()
    {
        return base.OnConnectedAsync();
    }

    public override Task OnDisconnectedAsync(Exception exception)
    {
        // 获取当前连接所有锁定的行,并移除反向映射条目
        if (ConnectionToRowLocks.TryRemove(Context.ConnectionId, out var userLockedRows))
        {
            foreach (var rowId in userLockedRows)
            {
                // 仅当当前连接持有该行锁时,才移除并通知其他客户端
                if (RowLocks.TryRemove(rowId, out var lockInfo) && lockInfo.ConnectionId == Context.ConnectionId)
                {
                    Clients.Others.SendAsync("RowUnlocked", rowId);
                }
            }
        }

        return base.OnDisconnectedAsync(exception);
    }

    public Task LockRow(string rowId, string module, string username)
    {
        // 获取当前连接的锁定行集合,不存在则创建新集合
        var userRows = ConnectionToRowLocks.GetOrAdd(Context.ConnectionId, _ => new HashSet<string>());
        
        // 锁定HashSet操作(HashSet非线程安全)
        lock (userRows)
        {
            userRows.Add(rowId);
        }

        // 更新行锁映射
        RowLocks[rowId] = (Context.ConnectionId, module, username);
        return Clients.Others.SendAsync("RowLocked", rowId, username);
    }

    public Task UnlockRow(string rowId)
    {
        if (RowLocks.TryRemove(rowId, out var lockInfo) && lockInfo.ConnectionId == Context.ConnectionId)
        {
            // 从反向映射中移除该行ID
            if (ConnectionToRowLocks.TryGetValue(Context.ConnectionId, out var userRows))
            {
                lock (userRows)
                {
                    userRows.Remove(rowId);
                    // 若集合为空,移除连接的反向映射条目以节省内存
                    if (userRows.Count == 0)
                    {
                        ConnectionToRowLocks.TryRemove(Context.ConnectionId, out _);
                    }
                }
            }

            return Clients.Others.SendAsync("RowUnlocked", rowId);
        }
        return Task.CompletedTask;
    }
}

关键修改说明

  • 新增反向映射:ConnectionToRowLocks记录每个连接ID对应的所有锁定行ID,确保断开连接时能找到所有需要释放的行。
  • LockRow方法:锁定行时,同时将行ID添加到当前连接的锁定集合中,同步维护双向映射关系。
  • UnlockRow方法:解锁行时,同步从反向映射中移除对应的行ID,避免无效条目残留。
  • OnDisconnectedAsync方法:断开连接时,通过反向映射获取该连接所有锁定的行,逐个校验并释放锁,同时通知其他客户端行已解锁。

页面端代码无需修改,保持原有的SignalR连接和事件监听逻辑即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 13:47:33