从串行端口线程安全解析数据

时间:2019-03-26 18:11:00

标签: c# multithreading serial-port

我从串行端口读取数据并将其解析为单独的类。但是,数据解析不正确,某些样本被重复,而另一些样本则丢失。

这是已解析数据包的示例。它以packetIndex开始(应从1开始递增)。您可以看到packetIdx如何重复,其他一些值也重复。我认为这是由于多线程导致的,但我不确定如何修复它。

    2 -124558.985180734 -67934.4168823262 -164223.049786454 -163322.386243628
    2 -124619.580759952 -67962.535376851 -164191.757344217 -163305.68949052
    3 -124685.719571795 -67995.8394760894 -164191.042088394 -163303.119039907 
    5 -124801.747477263 -68045.7062179692 -164195.288919841 -163299.140429394 
    6 -124801.747477263 -68045.7062179692 -164221.105184687 -163297.46404856 
    6 -124832.8387538 -68041.9287731563 -164214.936103217 -163294.983004926 

这是我应该收到的:

1 -124558.985180734 -67934.4168823262 -164223.049786454 -163322.386243628
2 -124619.580759952 -67962.535376851 -164191.757344217 -163305.68949052
3 -124685.719571795 -67995.8394760894 -164191.042088394 -163303.119039907 
4 -124801.747477263 -68045.7062179692 -164195.288919841 -163299.140429394 
 ...

这是SerialPort_DataReceived

 public void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        lock (_lock)
        {
            byte[] buffer = new byte[_serialPort1.BytesToRead];
            _serialPort1.Read(buffer, 0, buffer.Length);

            for (int i = 0; i < buffer.Length; i++)
            {
                //Parse data
                double[] samplesAtTimeT = DataParserObj.interpretBinaryStream(buffer[i]);
                //Add data to BlockingCollection when parsed 
                if (samplesAtTimeT != null)
                    _bqBufferTimerSeriesData.Add(samplesAtTimeT);
            }
        }
    }

以及解析数据的类:

public class DataParser
{
    private int packetSampleCounter = 0;
    private int localByteCounter = 0;
    private int packetState = 0;
    private byte[] tmpBuffer = new byte[3];
    private double[] ParsedData = new double[5]; //[0] packetIdx (0-255), [1-4] signal


    public double[] interpretBinaryStream(byte actbyte)
    {
        bool returnDataFlag = false;

        switch (packetState)
        {
            case 0: // end packet indicator
                if (actbyte == 0xC0)
                    packetState++;
                break;
            case 1: // start packet indicator
                if (actbyte == 0xA0)
                    packetState++;
                else
                    packetState = 0;
                break;
            case 2: // packet Index 
                packetSampleCounter = 0;
                ParsedData[packetSampleCounter] = actbyte;
                packetSampleCounter++;
                localByteCounter = 0;
                packetState++;
                break;
            case 3: //channel data (4 channels x 3byte/channel)
                // 3 bytes
                tmpBuffer[localByteCounter] = actbyte;
                localByteCounter++;
                if (localByteCounter == 3)
                {
                    ParsedData[packetSampleCounter] = Bit24ToInt32(tmpBuffer);
                    if (packetSampleCounter == 5)
                        packetState++; //move to next state, end of packet
                    else
                        localByteCounter = 0;
                }
                break;
            case 4: // end packet
                if (actbyte == 0xC0)
                {
                    returnDataFlag = true;
                    packetState = 1;
                }
                else
                    packetState = 0;
                break;
            default:
                packetState = 0;
                break;
        }
        if (returnDataFlag)
            return ParsedData;
        else
            return null;
    }
}

1 个答案:

答案 0 :(得分:0)

摆脱DataReceived事件,而改用await serialPort.BaseStream.ReadAsync(....)来通知数据何时进入。async / await更加干净,不会迫使您进入多线程数据处理。对于高速网络,并行处理非常有用。但是串行端口很慢,因此额外的线程没有好处。

此外,BytesToRead还是有问题的(它确实返回排队的字节数,但是会破坏其他状态),您永远不要调用它。

最后,不要忽略Read(或BaseStream.ReadAsync)的返回值。您需要知道字节实际上是如何放入缓冲区的,因为不能保证该字节数与您要求的相同。

private async void ReadTheSerialData()
{
     var buffer = new byte[200];
     while (serialPort.IsOpen) {
         var valid = await serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
         for (int i = 0; i < valid; ++i)
         {
             //Parse data
            double[] samplesAtTimeT = DataParserObj.interpretBinaryStream(buffer[i]);
            //Add data to BlockingCollection when parsed 
            if (samplesAtTimeT != null)
                _bqBufferTimerSeriesData.Add(samplesAtTimeT);
         }
     }
}

只需在打开端口并设置流控制,超时等之后调用此函数。您可能会发现不再需要阻塞队列,而可以直接处理samplesAtTimeT的内容。