我有很多客户端,每个客户端都有多个同时隧道(数据获取中间件)。
我必须使用每个客户端的实时隧道来管理所有客户端。
我的 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/