C# Async Serial Port Read

2019-02-05 08:55发布

问题:

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?

回答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)
        {
            int quedanPorLeer = count;
            var temp = new byte[count];

            while(quedanPorLeer > 0)
            {
                int leidos = await serialPort.BaseStream.ReadAsync(temp, 0, quedanPorLeer);
                Array.Copy(temp, 0, buffer, offset + count - quedanPorLeer, leidos);
                quedanPorLeer -= leidos;
            }
        }

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

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


回答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.