c# - 并发字典删除集合类型值的最佳方法

标签 c# multithreading dictionary collections concurrentdictionary

我有很多客户端,每个客户端都有多个同时隧道(数据获取中间件)。

我必须使用每个客户端的实时隧道来管理所有客户端。

我的 Tunnel 类具有许多属性和函数,我仅显示有用的属性:

 public class Tunnel : IEquatable<Tunnel>
 {
       public Guid UID { get; private set; }

        public Tunnel(){
            this.UID = Guid.NewGuid(); ;
        }
        public Tunnel(Guid uid_)
        {
            this.UID = uid_;
        }
       public bool Equals(Tunnel other)
     {
         return other != null && other.UID == this.UID;
     }

     public override bool Equals(object obj)
     {
         var other = obj as Tunnel;
         return other != null && other.UID == this.UID;
     }
     public override int GetHashCode()
     {
         return this.UID.GetHashCode();
     }

        public static bool operator ==(Tunnel tunnel1, Tunnel tunnel2)
        {
            if (((object)tunnel1) == null || ((object)tunnel2) == null)
            { return Object.Equals(tunnel1, tunnel2); }

            return tunnel1.Equals(tunnel2);
        }

        public static bool operator !=(Tunnel tunnel1, Tunnel tunnel2)
        {
            if (((object)tunnel1) == null || ((object)tunnel2) == null)
            { return !Object.Equals(tunnel1, tunnel2); }

            return !(tunnel1.Equals(tunnel2));
        }

      // 10+ other properties
 }

我有 ClientConnections 类,它管理所有客户端及其 LiveTunnel,如下所示:

public class ClientsConnections 
{
    internal readonly ConcurrentDictionary<Object, Dictionary<Guid, Tunnel>> ClientsSessions;
    
    public ClientsConnections(){
        this.ClientsSessions = new ConcurrentDictionary<object, Dictionary<Guid, Tunnel>>();
    }
    
    public Tunnel AddOrUpdateClientTunnel(Object ClientID, Tunnel tnl)
    {
        if (tnl.ClientID == null) { tnl.ClientID = ClientID; }
        this.ClientsSessions.AddOrUpdate(ClientID, new Dictionary<Guid, Tunnel>() { { tnl.UID, tnl } }, (oldkey, liveTunnels) =>
        {
            lock (liveTunnels)
            {
                if (liveTunnels.ContainsKey(tnl.UID))
                {
                    liveTunnels[tnl.UID] = tnl;
                }
                else
                {
                    liveTunnels.Add(tnl.UID, tnl);
                }
            }
            return liveTunnels;
        });
        return tnl;
    }
   
    public bool RemoveClientTunnel(Object ClientID, Tunnel tnl)
    {
        Boolean anyRemoved = false;
        
        // When there is no tunnel i.e. current tunnel is the last in ClientSessions, remove entire key value from Concurrent Dictionary
        if(this.ClientsSessions.ContainsKey(ClientID))
        {
           Dictionary<Guid, Tunnel> removedTunls;
           
            Dictionary<Guid, Tunnel> liveTunls = this.ClientsSessions[ClientID];
            lock (liveTunls) 
            {
                if (liveTunls.ContainsKey(tnl.UID))
                {
                    liveTunls.Remove(tnl.UID);
                   if(!anyRemoved){ anyRemoved = true;}
                }
            }
            if (liveTunls.Count == 0)
            {
                //No tunnels for this ClientID, remove this client from Concurrent Dictionary
                this.ClientsSessions.TryRemove(ClientID, out removedTunls);

                if (removedTunls.Count != 0)
                {
                    // Oops There were some Livetunnels, add them back
                    AddOrUpdateClientTunnelRestore(removedTunls);
                }
            }
        }

        return anyRemoved;
    }

    public bool AddOrUpdateClientTunnelRestore( Dictionary<Guid, Tunnel> tnltoAdd)
    {
        bool anyAdded = false;
        Object ClientId = tnltoAdd[tnltoAdd.Keys.First()].ClientID;
        this.ClientsSessions.AddOrUpdate(ClientId, tnltoAdd, (oldkey, liveTunnels) =>
        {
            lock (liveTunnels)
            {
                foreach (Tunnel tmpTnl in liveTunnels.Values)
                {
                    if (!liveTunnels.ContainsKey(tmpTnl.UID))
                    {
                        liveTunnels.Add(tmpTnl.UID, tmpTnl);
                        if (!anyAdded) { anyAdded = true; }
                    }
                }
            }
            return liveTunnels;
        });
        return anyAdded;
    }

}

当客户端没有 LiveTunnel 时,必须从 ConcurrentDictionary 中删除整个客户端。

有没有更好的方法来做到这一点,特别是在函数 RemoveClientTunnel 中?

隧道:包含 10 多个带有数据库连接和套接字连接的属性。

对于当前场景,大约有 10,000 多个客户端,每个客户端至少有 2 到 4 个 LiveTunnel,平均每个客户端有 8 到 10 个 LiveTunnel。

频率:在某些持续时间内,客户端连接频率较高。例如,上午 9:30 所有客户端开始连接,中午 12 点左右客户端开始断开连接(30-50%),下午 2 点左右客户端重新连接,下午 5 点客户端开始断开连接。

从上午 9:30 起流量就很大。隧道的频率:每个客户端至少保持隧道1-2秒。最低限度。如果我们计算隧道保持的最短持续时间,最少为 1-2 秒。隧道持续时间没有最大时间限制。客户端可以在很长一段时间内(18 小时以上)持有任意数量的隧道

最佳答案

好吧,您的 RemoveClientTunnel 从根本上被破坏了,所以很难找到一个更糟糕的方法来真正做到这一点,除非我们对它可以变得更糟糕进行分级.

具体来说,您在多线程环境中使用 ContainsKey 后跟 this[] 反模式,如果两个不同的线程处于竞争状态,则这是一种竞争条件尝试删除 session 。至少,该函数中的锁应该移至 ContainsKey 之上,或者更好地使用为您处理原子性的复合函数,例如 TryGet尝试更新

换句话说,要么您自己处理原子性(例如使用锁,或者甚至更好的更精细的东西,不需要您在每个循环中运行内核代码,例如ReaderWriterLockSlim),或者您完全依赖 ConcurrentDictionary 实现,但随后您必须实际使用它的原语,而不是执行这种在调用之间随机降低原子性的代码大杂烩。在足够长的时间轴上,您将在 lock (liveTunls) 行上看到空引用异常,因为索引器找不到 ContainsKey 声明的项目。

关于c# - 并发字典删除集合类型值的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72060540/

相关文章:

c# - AsyncPostBackTrigger Gridview 分页

python - 使用 type(a) 作为字典键是否合理?

pandas - 将 Pandas 数据帧转换为具有多个键的字典

java - 为什么线程会在我的 actionListener 实现中卡住我的代码?

Swift 字典数组

C#剪贴板直接复制粘贴

c# - 单击按钮时动态添加的文本框为空

c# - 动态设置 opentok 中 token 的 EXPIRE_TIME 从 .net 中的当前时间起 15 分钟

c++ - 如何避免 recursive_mutex

c++ - 选择调用似乎没有超时