C# Async Serial Port Read

31,962

Solution 1

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

Solution 2

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.

Share:
31,962
mikeminer
Author by

mikeminer

Updated on March 25, 2020

Comments

  • mikeminer
    mikeminer about 4 years

    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?

  • mikeminer
    mikeminer almost 10 years
    Hans, thanks a lot for the response. In the event that there are less than 5 bytes initially received, it seems to stop and I need to figure out how to "loop" back again. So for example, I am always reading in an array of 20 bytes. The serial port usually pulls in all 20 at once, but sometimes it pulls in, say 1 byte, then 19. In that case, the code above reads 1 byte, then stops. Do I need a sleep somewhere to give the rest of the data time to come in?
  • user1703401
    user1703401 almost 10 years
    The DataReceived event should fire again when the rest of the bytes arrive. You should never do anything like "loop back again". I have no idea why that doesn't happen of course. Some bug where you actually read the bytes but not actually use them is common.
  • MagicLegend
    MagicLegend over 7 years
    FWI: I had to use System.IO.Ports.SerialPort serialPort instead of just SerialPort serialPort. Dunno if I goofed something up, but it only wants to work that way. Yes, I'm using System.IO.Ports. --- But, I still can't get it to read anything :(
  • Felix
    Felix over 7 years
    but I found the ReadTimeout is not working on BaseStream
  • user3717478
    user3717478 almost 7 years
    Here is how to add ad timeout: stackoverflow.com/a/41193744/3717478