Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Async Serial Port Read

I have a class which reads from the serial port using the DataReceived event handler in C#. When I receive data, I know the header will have 5 bytes, so I don't want to do anything with the data until I have at least that. My current code is below:

while (serialPort.BytesToRead<5)
{
//Do nothing while we have less bytes than the header size
}

//Once at least 5 bytes are received, process header

As I understand it, this code is blocking and needs to be improved. I'm looking for suggestions on how to do this. Would another event handler inside the DataReceived event handler be appropriate?

like image 502
mikeminer Avatar asked Jun 04 '14 15:06

mikeminer


2 Answers

Use async programming (don't forget to target first your application to .NET Framework 4.5).

Here you've my implementation as extension methods for SerialPort.

using System;
using System.IO.Ports;
using System.Threading.Tasks;

namespace ExtensionMethods.SerialPort
{
    public static class SerialPortExtensions
    {
        public async static Task ReadAsync(this SerialPort serialPort, byte[] buffer, int offset, int count)
        {
            var bytesToRead = count;
            var temp = new byte[count];

            while (bytesToRead > 0)
            {
                var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);
                Array.Copy(temp, 0, buffer, offset + count - bytesToRead, readBytes);
                bytesToRead -= readBytes;
            }
        }

        public async static Task<byte[]> ReadAsync(this SerialPort serialPort, int count)
        {
            var buffer = new byte[count];
            await serialPort.ReadAsync(buffer, 0, count);
            return buffer;
        }
    }
}

and here how to read:

public async void Work()
{
   try
   {
       var data = await serialPort.ReadAsync(5);
       DoStuff(data);
   }
   catch(Exception excepcion)
   {
       Trace.WriteLine(exception.Message);
   }
}
like image 109
joseangelmt Avatar answered Nov 16 '22 06:11

joseangelmt


That burns 100% core, you don't want to do that. The proper way is to have your program block on the Read() call. You'd write it similar to this:

private byte[] rcveBuffer = new byte[MaximumMessageSize];
private int rcveLength;

void ReceiveHeader() {
    while (rcveLength < 5) {
        rcveLength += serialPort.Read(rcveBuffer, rcveLength, 5 - rcveLength);
    }
}

Or if you use the DataReceived event then it can look like this:

    private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) {
        if (e.EventType != System.IO.Ports.SerialData.Chars) return;
        if (rcveLength < 5) {
            rcveLength += serialPort.Read(rcveBuffer, rcveLength, 5 - rcveLength);
        }
        if (rcveLength >= 5) {
            // Got the header, read the rest...
        }
    }

Don't forget to set rcveLength back to 0 after you've got the entire message and processed it.

like image 43
Hans Passant Avatar answered Nov 16 '22 05:11

Hans Passant