fzkqi 发表于 2019-8-1 10:49:05

c# List<byte> 缓存串口接收到的数据偶尔丢失

大家好,我们设备用的自定义的串口通讯协议,最近写了一个解包的类,在_serialPort_DataReceived接收事件中读取数据。然后将读取到的数据传到解包类。
解包类先用List<byte>.AddRange把传进来的bytes保存起来,然后对这个List<byte>进行一个一个字节判断解析。解析到正确的数据就包这些数据从List中移除。

我在这个解包函数进入和退出的地方各加了一个打印,打印当前该List<byte>的数量,同时在所有操作这个List<byte>的地方都加了打印。
现在遇到一个怪现象,而且这个现象不是必现:

上次退出函数时候,List<byte>的数量是一个值,在没有其他任何操作该List<byte>的情况下。下次进入时候,突然这个List<byte>的数量变了,里面的数据也变了,
导致数据接收异常。日志记录基本上是这样的。
exit parse,cache len=32
into parse,cache len = 4
中间没有其他操作记录,这个List<byte>的 数量莫名其妙的变了。

最关键是这个现象还不是必现,有时候是正常的,有时候不正常。

浮华一生 发表于 2019-8-1 10:59:52

List 不是线程安全的   你需要的是 ConcurrentQueue

fzkqi 发表于 2019-8-1 11:19:01

浮华一生 发表于 2019-8-1 10:59
List 不是线程安全的   你需要的是 ConcurrentQueue

您好,您的意思_serialPort_DataReceived这个事件可能会出现上一次未退出,下一次又开始执行是吗?否则的话,这个List应该总是一个线程在访问

wangchin1988 发表于 2019-8-1 11:26:21

浮华一生 发表于 2019-8-1 10:59
List 不是线程安全的   你需要的是 ConcurrentQueue

谢谢!涨姿势了{:lol:}

浮华一生 发表于 2019-8-1 11:27:07

fzkqi 发表于 2019-8-1 11:19
您好,您的意思_serialPort_DataReceived这个事件可能会出现上一次未退出,下一次又开始执行是吗?否则的 ...

呃, DataReceived 事件是IO线程调用的。新数据进来是IO线程操作你的list    你解析除非也在这个事件的处理函数里面,否则怎么会是在同一个线程呢。具体你把代码贴上来看看

openmcu666 发表于 2019-8-1 11:28:46

控件跨线程操作,可以用事件

fzkqi 发表于 2019-8-1 11:47:24

浮华一生 发表于 2019-8-1 11:27
呃, DataReceived 事件是IO线程调用的。新数据进来是IO线程操作你的list    你解析除非也在这个事件的 ...

您看一下。我对整个Parse加了lock还是一样会出问题。

private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    int count = sp.BytesToRead;
    if (count > 0)
    {
      byte[]buffer = new byte;
      sp.Read(buffer, 0, count);
      List<byte[]> packets = protocol.Parse(buffer); // protocol这个类调用解析,返回解析后的数据包

      if (packets.Count > 0)
      {
            if (OnFrameReceived != null) // 处理事件
            {
                foreach(byte[]packet in packets)
                {
                  OnFrameReceived(packet);
                }
            }
      }
    }
}

// 解析,返回的List<byte[]> 包含了解析到的数据包
public List<byte[]> Parse(byte[]bytes)
{
    List<byte[]> frames = new List<byte[]>();
    lock(_recvCacheLock)
    {
      Debug.WriteLine("into parse.len={0}", _recvCache.Count); // 这是我加的计数日志
      _recvCache.AddRange(bytes);
      Debug.WriteLine("add parse.len={0}", _recvCache.Count);

      while (true)
      {
            if (解析成功)
            {
                /* 具体解析就省略了,这里仅作示意 */
                byte[] frame = new byte;
                _recvCache.CopyTo(frame); // 将解析正确的数据复制到frame
                _recvCache.RemoveRange(0, length); // 移除已经解析到的
                frames.Add(frame); // 添加到解析好的List列表
            }
      }
      Debug.WriteLine("exit parse.len={0}", _recvCache.Count);
    }
    return frames;
}

浮华一生 发表于 2019-8-1 12:03:26

呃 你打印的 into parse,cache len = 4只能说明是加入新数据之前 _recvCache 中的数据啊。exit parse.len 是你加了之后的, 你可以在对所有的 _recvCache 添加移除 操作加debug看看是否符合你的逻辑。 我感觉可能是你解析部分有问题。 BTW, 你这个结构不太好的感觉,数据量大点,或者你处理时间长点,可能丢包的感觉啊 {:lol:}

fzkqi 发表于 2019-8-1 13:43:59

浮华一生 发表于 2019-8-1 12:03
呃 你打印的 into parse,cache len = 4只能说明是加入新数据之前 _recvCache 中的数据啊。exit parse.len ...

您好,我的打印日志是这样的
exit parse,cache len=32
into parse,cache len = 4

重点是上一次退出时候是一个长度,下一次再进入时又变成另一个长度。
而且我的打印日志很干净都是
into
exit
into
exit
都是into和exit成对的连续的出现。没有并发的调用情况。

windboy 发表于 2019-8-1 13:46:27

2楼回复正确

浮华一生 发表于 2019-8-1 15:02:02

fzkqi 发表于 2019-8-1 13:43
您好,我的打印日志是这样的
exit parse,cache len=32
into parse,cache len = 4


那你可以看一下引用情况,是不是其他哪里做了修改。
页: [1]
查看完整版本: c# List<byte> 缓存串口接收到的数据偶尔丢失