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

C# TCP客户端存储:如何实现线程安全的List<T>?

C# TCP监听程序中客户端连接存储的线程安全问题解答

你的两种写法是否线程安全?会导致列表损坏吗?

1. clientList.AsParallel().ForAll(item => clientList.Add(client));

这种写法完全不线程安全,必然会导致List<TcpClient>损坏。

  • List<T>本身是非线程安全集合,其Add操作没有任何同步机制,多线程同时调用Add会破坏列表内部的计数、数组扩容逻辑或元素存储结构,可能引发IndexOutOfRangeException、元素丢失、计数错误,甚至直接导致列表处于不可用的损坏状态。
  • AsParallel()会启用多线程并行执行Add操作,相当于多个线程同时对非线程安全的列表进行修改,风险直接拉满。

2. 带lock的Parallel.ForEach写法

这种写法依然不安全,且逻辑存在明显问题:

  • 首先,Parallel.ForEach遍历clientList的同时,在循环体内修改该集合(调用Add),即使加了lock,也会触发枚举器的失效问题,大概率抛出InvalidOperationException(集合已修改,无法枚举)。
  • 其次,lock (clientList)不是最佳实践(应使用专用的私有锁对象),但更核心的问题是:你遍历现有客户端的目的是添加同一个client?这会导致同一个客户端被重复添加N次(N为当前列表长度),逻辑上完全错误。
  • 退一步说,即使逻辑没问题,这种并行添加的写法毫无意义——添加客户端是单个操作,并行执行只会增加同步开销,没有任何性能收益。

正确的线程安全实现方式

方案1:使用官方线程安全集合(推荐)

直接替换List<TcpClient>为.NET提供的线程安全集合,无需手动处理同步:

  • 如果不需要客户端的顺序,推荐使用ConcurrentBag<TcpClient>:
    private ConcurrentBag<TcpClient> _clientBag = new ConcurrentBag<TcpClient>();
    
    // 添加新客户端
    _clientBag.Add(newClient);
    
    // 遍历客户端(获取快照遍历,避免枚举期间的修改影响)
    foreach (var client in _clientBag.ToArray())
    {
        // 处理客户端逻辑
    }
    
  • 如果需要按连接顺序处理客户端,使用ConcurrentQueue<TcpClient>:
    private ConcurrentQueue<TcpClient> _clientQueue = new ConcurrentQueue<TcpClient>();
    
    // 添加新客户端
    _clientQueue.Enqueue(newClient);
    
    // 遍历或取出客户端
    if (_clientQueue.TryDequeue(out var client))
    {
        // 处理客户端逻辑
    }
    

这些集合的所有操作都是线程安全的,内部已经实现了高效的同步机制,无需额外加锁。

方案2:保留List<TcpClient>并手动加锁

如果必须使用List<T>,需确保所有对列表的读写操作都在同一个锁的保护下,且避免遍历期间修改集合:

// 使用专用的私有锁对象,不要用集合本身作为锁
private readonly object _clientLock = new object();
private List<TcpClient> _clientList = new List<TcpClient>();

// 添加新客户端
lock (_clientLock)
{
    _clientList.Add(newClient);
}

// 遍历客户端(先获取快照,避免遍历期间修改集合引发异常)
List<TcpClient> clientSnapshot;
lock (_clientLock)
{
    clientSnapshot = _clientList.ToList();
}
foreach (var client in clientSnapshot)
{
    // 处理客户端逻辑
}

// 删除客户端时同样需要加锁
lock (_clientLock)
{
    _clientList.Remove(clientToRemove);
}

注意:锁对象必须是私有、只读的,避免外部代码对其加锁导致死锁风险;所有涉及列表修改或读取的操作都必须进入锁保护的代码块。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 20:25:11