简体   繁体   English

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

[英]Awaiting data from Serial Port in C#

I have an application that receives data from a wireless radio using RS-232. 我有一个使用RS-232从无线电接收数据的应用程序。 These radios use an API for communicating with multiple clients. 这些无线电使用API​​与多个客户端进行通信。 To use the radios I created a library for communicate with them that other software can utilize with minimal changes from a normal SerialPort connection. 为了使用无线电,我创建了一个与它们进行通信的库,其他软件可以利用这些库与常规SerialPort连接进行最小的更改。 The library reads from a SerialPort object and inserts incoming data into different buffers depending on the radio it receives from. 该库从SerialPort对象读取,然后根据接收到的无线电将输入的数据插入不同的缓冲区。 Each packet that is received contains a header indicating its length, source, etc. 每个接收到的数据包都包含一个标头,指示其长度,源等。

I start by reading the header, which is fixed-length, from the port and parsing it. 我首先从端口读取固定长度的标头并进行解析。 In the header, the length of the data is defined before the data payload itself, so once I know the length of the data, I then wait for that much data to be available, then read in that many bytes. 在标头中,数据的长度是在数据有效载荷本身之前定义的,因此一旦我知道了数据的长度,便会等待那么多的数据可用,然后读入那么多的字节。

Example (the other elements from the header are omitted): 示例(省略标题中的其他元素):

// 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);

Obviously the empty while port is bad. 显然,空端口是坏的。 If for some reason the data never arrives the thread will lock. 如果由于某种原因数据永不到达,线程将锁定。 I haven't encountered this problem yet, but I'm looking for an elegant way to do this. 我还没有遇到这个问题,但是我正在寻找一种优雅的方式来做到这一点。 My first thought is to add a short timer that starts just before the while-loop, and sets an abortRead flag when it elapses that would break the while loop, like this: 我的第一个想法是添加一个简短的计时器,该计时器从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) {}

This code needs to handle a constant stream of incoming data as quickly as it can, so keeping overhead to a minimum is a concern, and am wondering if I am doing this properly. 此代码需要尽快处理恒定的传入数据流,因此将开销降至最低是一个问题,并且想知道我是否做得正确。

If you want to truly adress this problem, you need to run the code in the background. 如果您想真正解决此问题,则需要在后台运行代码。 There are different options to do that; 有不同的选择可以做到这一点。 you can start a thread, you start a Task or you can use async await . 您可以启动线程,启动Task或使用async await

To fully cover all options, the answer would be endless. 要完全涵盖所有选项,答案将是无穷无尽的。 If you use threads or tasks with the default scheduler and your wait time is expected to be rather short, you can use SpinWait.SpinUntil instead of your while loop. 如果您使用带有默认调度程序的线程或任务,并且等待时间预计会很短,则可以使用SpinWait.SpinUntil而不是while循环。 This will perform better than your solution: 这将比您的解决方案更好地执行:

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

If you are free to use async await , I would recommend this solution, since you need only a few changes to your code. 如果您可以随意使用async await ,那么我建议您使用此解决方案,因为您只需对代码进行一些更改即可。 You can use Task.Delay and in the best case you pass a CancellationToken to be able to cancel your operation: 您可以使用Task.Delay ,在最佳情况下,您可以传递CancellationToken来取消操作:

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

You don't have to run this while loop, the method Read would either fill the buffer for you or would throw a TimeoutException if buffer wasn't filled within the SerialPort.ReadTimeout time (which you can adjust to your needs). 您不必运行while循环, Read方法将为您填充缓冲区,或者如果在SerialPort.ReadTimeout时间(您可以根据需要进行调整)内未填充缓冲区,则将抛出TimeoutException

But some general remark - your while loop would cause intensive CPU work for nothing, in the few milliseconds it would take the data to arrive you would have thousends of this while loop iterations, you should've add some Thread.Sleep inside. 但是有一些一般性的说明-while循环将导致大量的CPU工作,而在数毫秒之内将需要花费数据才能到达,而while循环迭代将有大量的这种情况,您应该在其中添加一些Thread.Sleep

I think I would do this asynchronously with the SerialPort DataReceived event. 我想我可以通过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