C#多线程ConcurrentQueue入出队 无重复却出队重复数据问题
问题根因
ConcurrentQueue本身是线程安全的队列实现,不会主动产生重复数据,你遇到的重复、数据拼接问题完全是入队端代码错误复用引用类型对象导致的,核心问题有两个:
- 入队对象和接收缓冲区在循环外初始化,所有入队元素共享同一块内存
- 入队后没有做数据隔离,并发读写同一块内存导致脏读、覆盖
具体错误点拆解
- 对象复用导致队列里全是同一个引用
看你的入队线程代码,ReceiveDataType InsertQueue和byte[] data = new byte[1024]都是在while(true)循环外定义的,整个线程生命周期里只创建了1次InsertQueue实例、1块1024字节的缓冲区。你每次循环入队的,本质是同一个对象的引用,所有队列节点都指向这同一块内存,根本不是独立的数据副本。 - 并发覆写导致出队读到重复/脏数据
你调用Enqueue之后,没有做任何等待或者数据拷贝,下一轮循环的ReceiveFrom会直接把新收到的UDP数据覆写到同一个字节数组里。这时候出队线程哪怕拿到的是不同的队列节点,读的都是同一块被反复覆写的内存,自然会出现多个节点读到同一份最新数据(也就是你看到的重复),甚至读到半新半旧的拼接数据——你日志里最后那条把两条消息拼在一起的异常内容,就是写缓冲区写到一半被读线程打断读到的脏数据。 - 你之前“队列里没有重复数据”的判断是错的
你用来做校验的Enqueue.txt日志,是在Enqueue之后才去读共享缓冲区转字符串写文件,这时候缓冲区完全可能已经被下一轮接收的数据覆盖,写进去的内容根本不是当时入队的原始数据,不能作为队列无重复的判断依据。
修复方案
- 把缓冲区、入队对象的初始化移到循环内部,每次接收前创建全新的独立实例,从根源上避免共享内存
- 保存每次UDP接收的实际长度,转字符串时只处理有效字节,避免把缓冲区里的空字节、残留旧数据读出来
修正后的入队核心代码参考:
private void Server_MulticastReceiveThread() { Socket mcastSocket; int recv; while (true) { try { // 每次循环新建独立缓冲区和入队对象,不复用 byte[] data = new byte[1024]; ReceiveDataType InsertQueue = new ReceiveDataType(); recv = mcastSocket.ReceiveFrom(data, ref remoteEndPoint); InsertQueue.data = data; // 给你的ReceiveDataType加个存实际接收长度的字段,避免读多余字节 InsertQueue.ActualLength = recv; Server_Queue.Enqueue(InsertQueue); // 写日志只转有效长度的字节 string path = @"C:\Users\SAN\Downloads\Enqueue.txt"; string logContent = Encoding.Default.GetString(data, 0, recv); using (StreamWriter sw = File.AppendText(path)) { sw.WriteLine(logContent); } } catch (Exception e) { Thread.Sleep(INTERVAL_30); continue; } } }
出队端解析数据时,也用实际长度转字符串:
rxSentence = Encoding.Default.GetString(ExtractQueue.data, 0, ExtractQueue.ActualLength);
如果需要更严谨的排查,可以给每个入队对象加一个自增的序列号,入队时赋值,出队时打日志,就能直接确认队列里有没有重复入队的节点。
内容的提问来源于stack exchange,提问作者KooEunBam
相关产品推荐
相关产品推荐

