namespace Dpz.Core.Web.Library;

[Obsolete]
public class ChatCurrentConnections
{
    /// <summary>
    /// 在线用户
    /// </summary>
    private static readonly Dictionary<string, VmUserInfo> ConnectionUsers =
        new Dictionary<string, VmUserInfo>();

    private static readonly object LockObj = new object();

    private static ChatCurrentConnections? _instance;

    private ChatCurrentConnections() { }

    public static ChatCurrentConnections GetInstance()
    {
        if (_instance != null)
        {
            return _instance;
        }

        lock (LockObj)
        {
            _instance = new ChatCurrentConnections();
            return _instance;
        }
    }

    public VmUserInfo this[string index] => ConnectionUsers[index];

    public void Add(string connectionId, VmUserInfo user)
    {
        lock (LockObj)
        {
            if (ConnectionUsers.ContainsKey(connectionId))
            {
                ConnectionUsers.Add(connectionId, user);
            }
            else
            {
                ConnectionUsers[connectionId] = user;
            }
        }
    }

    public VmUserInfo? Get(string connectionId)
    {
        if (!ConnectionUsers.TryGetValue(connectionId, out var user))
        {
            return null;
        }
        return user;
    }

    public void Remove(string connectionId)
    {
        if (ConnectionUsers.ContainsKey(connectionId))
        {
            //ConnectionUsers.Values.Any(x => x.Id == this[connectionId].Id)
            lock (LockObj)
            {
                ConnectionUsers.Remove(connectionId);
            }
        }
    }

    public List<VmUserInfo> GetAllOnlineUsers()
    {
        lock (LockObj)
        {
            return ConnectionUsers.Select(x => x.Value).ToList();
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

下面按要点说明这段代码的功能、各成员作用、存在的问题(尤其是并发/逻辑错误),以及可行的改进建议。

功能概述

  • 这是一个(意图上的)单例类 ChatCurrentConnections,用来维护当前在线用户的映射:以 connectionId(例如 SignalR 的连接 id)为键,VmUserInfo 为值,保存到静态字典 ConnectionUsers 中。
  • 提供添加、查询、移除单个连接,以及获取所有在线用户列表的操作。
  • 类上标记了 [Obsolete],表示该类已被标记为过时或建议不要再使用(源代码作者可能有替代实现)。

成员和行为

  • private static readonly Dictionary<string, VmUserInfo> ConnectionUsers:存储在线用户的字典(静态,共享)。
  • private static readonly object LockObj:用于加锁的对象,意图实现线程同步。
  • private static ChatCurrentConnections? _instance:单例实例字段。
  • private ChatCurrentConnections():私有构造函数,防止外部直接 new。
  • public static ChatCurrentConnections GetInstance():返回单例实例(当前实现试图用锁保证线程安全)。
  • public VmUserInfo this[string index] => ConnectionUsers[index]:索引器,直接通过 key 取值(无锁)。
  • public void Add(string connectionId, VmUserInfo user):将 connectionId 与 user 关联(在方法体中有锁)。
  • public VmUserInfo? Get(string connectionId):尝试从字典中取值(无锁)。
  • public void Remove(string connectionId):移除指定 connectionId(先检查 ContainsKey,再在锁内 Remove)。
  • public List GetAllOnlineUsers():在锁内把字典的值拷贝成列表返回。

存在的问题和 BUG(重要)

  1. Add 方法逻辑错误(严重)

    • 代码: if (ConnectionUsers.ContainsKey(connectionId)) { ConnectionUsers.Add(connectionId, user); } else { ConnectionUsers[connectionId] = user; }
    • 意图通常是:如果已存在则覆盖,否则 Add。但当前条件写反了:当字典已有 key 时调用 Add 会抛出 ArgumentException,反而当不存在时使用索引器设置。
    • 正确应为: if (!ConnectionUsers.ContainsKey(connectionId)) ConnectionUsers.Add(connectionId, user); else ConnectionUsers[connectionId] = user;
  2. 单例实现非严格正确(可能创建多次实例)

    • GetInstance 先检查 _instance != null 再 lock,然后在 lock 内直接 new,不再次检查 _instance 是否已经被其他线程创建。这样在两个线程同时通过外部检查后,两个线程都能进入 lock(第二个会等待),第一个创建实例后第二个进入 lock 时又会再创建一个实例并覆盖 _instance。
    • 解决办法:在 lock 内再判断一次(双重检查),或使用更简单且安全的模式,例如静态只读实例或 Lazy
  3. 字典的并发访问不一致

    • 虽然很多操作使用 LockObj 加锁,但并非所有读操作都加锁(例如索引器和 Get 方法以及 ContainsKey 前的检查是无锁或分开锁的)。这会导致竞态条件:在无锁读取时字典可能被修改,造成异常(例如在枚举时抛出)、不一致读取或 KeyNotFoundException。
    • Remove 方法先做 ContainsKey 再在 lock 内 Remove,期间状态可能被其他线程改变(虽然不会抛,但逻辑不严谨)。
    • 返回 List 的做法是把值拷贝了一份(在 lock 内),这是好的——避免外部枚举原始字典。

改进建议(简明)

  • 修复 Add 的条件逻辑(见上)。
  • 改进单例实现:
    • 推荐使用 readonly 静态初始化或 Lazy
      • static readonly ChatCurrentConnections Instance = new ChatCurrentConnections();
      • 或 public static ChatCurrentConnections GetInstance() => LazyInstance.Value; private static readonly Lazy LazyInstance = new(...);
    • 或在 lock 中使用双重检查: lock(...) { if (_instance == null) _instance = new ...; }
  • 使用线程安全的集合来简化并发处理:
    • 将 Dictionary 换成 ConcurrentDictionary<string, VmUserInfo>,这样很多操作可以用 TryAdd/TryUpdate/TryRemove/[] 原子完成,减少手工加锁的错误。
  • 保证所有对共享字典的读取/写入要么都在锁内,要么都使用线程安全集合,避免无锁读取和有锁写入混用。
  • 索引器应当考虑返回 null 或抛更明确的异常,或者在索引器访问前保证 key 存在(当前直接 ConnectionUsers[index] 会在不存在时抛 KeyNotFoundException)。
  • 如果类被标为 Obsolete,最好查找并使用推荐的替代实现(例如应用程序中可能已有更好的在线用户管理方案)。

示例:用 ConcurrentDictionary 的简化版(伪代码)

  • private static readonly ConcurrentDictionary<string, VmUserInfo> ConnectionUsers = new();
  • Add: ConnectionUsers[connectionId] = user; 或 ConnectionUsers.AddOrUpdate(...)
  • Get: ConnectionUsers.TryGetValue(connectionId, out var u) ? u : null
  • Remove: ConnectionUsers.TryRemove(connectionId, out _)
  • GetAllOnlineUsers: return ConnectionUsers.Values.ToList();

总结

  • 这段代码的目的是作为单例管理当前在线用户(connectionId -> VmUserInfo)。
  • 实现中存在重要的并发与逻辑问题(Add 的反向判断、单例可能被多次创建、读写锁使用不一致),建议用 ConcurrentDictionary 或正确的单例/锁策略进行修正。若需要,我可以把这份代码改成更安全、简洁的版本(比如用 ConcurrentDictionary + Lazy),并贴出完整修正后的代码。要不要我把代码修正并给出具体实现?
评论加载中...