C# Async Serial Port Read
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.
mikeminer
Updated on March 25, 2020Comments
-
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 almost 10 yearsHans, 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 almost 10 yearsThe 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 over 7 yearsFWI: I had to use
System.IO.Ports.SerialPort serialPort
instead of justSerialPort serialPort
. Dunno if I goofed something up, but it only wants to work that way. Yes, I'musing System.IO.Ports
. --- But, I still can't get it to read anything :( -
Felix over 7 yearsbut I found the
ReadTimeout
is not working onBaseStream
-
user3717478 almost 7 yearsHere is how to add ad timeout: stackoverflow.com/a/41193744/3717478