繁体   English   中英

在C#中等待来自串行端口的数据

[英]Awaiting data from Serial Port in C#

我有一个使用RS-232从无线电接收数据的应用程序。 这些无线电使用API​​与多个客户端进行通信。 为了使用无线电,我创建了一个与它们进行通信的库,其他软件可以利用这些库与常规SerialPort连接进行最小的更改。 该库从SerialPort对象读取,然后根据接收到的无线电将输入的数据插入不同的缓冲区。 每个接收到的数据包都包含一个标头,指示其长度,源等。

我首先从端口读取固定长度的标头并进行解析。 在标头中,数据的长度是在数据有效载荷本身之前定义的,因此一旦我知道了数据的长度,便会等待那么多的数据可用,然后读入那么多的字节。

示例(省略标题中的其他元素):

// Read header
byte[] header = new byte[RCV_HEADER_LENGTH];
this.Port.Read(header, 0, RCV_HEADER_LENGTH);

// Get length of data in packet
short dataLength = header[1];
byte[] payload = new byte[dataLength];

// Make sure all the payload of this packet is ready to read
while (this.Port.BytesToRead < dataLength) { }

this.Port.Read(payload, 0, dataLength);

显然,空端口是坏的。 如果由于某种原因数据永不到达,线程将锁定。 我还没有遇到这个问题,但是我正在寻找一种优雅的方式来做到这一点。 我的第一个想法是添加一个简短的计时器,该计时器从while循环之前开始,并在其终止时设置abortRead标志,这会中断while循环,如下所示:

// Make sure all the payload of this packet is ready to read
abortRead = false;
readTimer.Start();
while (this.Port.BytesToRead < dataLength && !abortRead) {}

此代码需要尽快处理恒定的传入数据流,因此将开销降至最低是一个问题,并且想知道我是否做得正确。

如果您想真正解决此问题,则需要在后台运行代码。 有不同的选择可以做到这一点。 您可以启动线程,启动Task或使用async await

要完全涵盖所有选项,答案将是无穷无尽的。 如果您使用带有默认调度程序的线程或任务,并且等待时间预计会很短,则可以使用SpinWait.SpinUntil而不是while循环。 这将比您的解决方案更好地执行:

SpinWait.SpinUntil(() => this.Port.BytesToRead >= dataLength);

如果您可以随意使用async await ,那么我建议您使用此解决方案,因为您只需对代码进行一些更改即可。 您可以使用Task.Delay ,在最佳情况下,您可以传递CancellationToken来取消操作:

try {
    while (this.Port.BytesToRead < dataLength) {
        await Task.Delay(100, cancellationToken);
    }
}
catch(OperationCancelledException) {
    //Cancellation logic
}

您不必运行while循环, Read方法将为您填充缓冲区,或者如果在SerialPort.ReadTimeout时间(您可以根据需要进行调整)内未填充缓冲区,则将抛出TimeoutException

但是有一些一般性的说明-while循环将导致大量的CPU工作,而在数毫秒之内将需要花费数据才能到达,而while循环迭代将有大量的这种情况,您应该在其中添加一些Thread.Sleep

我想我可以通过SerialPort DataReceived事件来异步执行此操作。

// Class fields
private const int RCV_HEADER_LENGTH = 8;
private const int MAX_DATA_LENGTH = 255;

private SerialPort Port;

private byte[] PacketBuffer = new byte[RCV_HEADER_LENGTH + MAX_DATA_LENGTH];
private int Readi = 0;
private int DataLength = 0;


// In your constructor
this.Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);


private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
    if (e.EventType != SerialData.Chars)
    {
        return;
    }

    // Read all available bytes.
    int len = Port.BytesToRead;
    byte[] data = new byte[len];
    Port.Read(data, 0, len);

    // Go through each byte.
    for (int i = 0; i < len; i++)
    {
        // Add the next byte to the packet buffer.
        PacketBuffer[Readi++] = data[i];

        // Check if we've received the complete header.
        if (Readi == RCV_HEADER_LENGTH)
        {
            DataLength = PacketBuffer[1];
        }

        // Check if we've received the complete data.
        if (Readi == RCV_HEADER_LENGTH + DataLength)
        {
            // The packet is complete add it to the appropriate buffer.

            Readi = 0;
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM