简体   繁体   中英

Awaiting data from Serial Port in C#

I have an application that receives data from a wireless radio using RS-232. These radios use an API for communicating with multiple clients. 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. The library reads from a SerialPort object and inserts incoming data into different buffers depending on the radio it receives from. 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:

// 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 .

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. 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. You can use Task.Delay and in the best case you pass a CancellationToken to be able to cancel your operation:

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).

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.

I think I would do this asynchronously with the SerialPort DataReceived event.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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