SerialPort.BaseStream.ReadAsync drops or scrambles bytes when reading from a USB Serial Port
I finally came up with an answer after stepping through the decompiled source code for the .Net SerialPort class (with resharper installed just Rclick on SerialPort->Navigate->Decompiled Sources
).
Answer #1: The bytes out of order problem was due to an error earlier in my program. I had canceled and restarted the readAsync loop, but I was using the wrong cancellation token so there were two copies of the loop both awaiting readAsync from the serial port. Both were issuing interrupts to return the received data, but of course it was a race condition as to which one got there first.
Answer #2: Note the way I am using the synchronous read method: I don't use the Received event (which doesn't work correctly) or check the number of bytes to read (which is unreliable) or anything like that. I merely set a timeout of zero, attempt to read with a large buffer, and check how many bytes I got back.
When called in this way, the synchronous SerialPort.Read first attempts to fulfill a read request from an internal cache[1024] of data bytes received. If it still doesn't have enough data to meet the request, it then issues a ReadAsync request against the underlying BaseStream using the exact same buffer, (adjusted)offset, and (adjusted)count.
Bottom line: When used the way I am using it, the synchronous SerialPort.Read method behaves exactly like SerialPort.ReadAsync. I conclude that it would probably be fine to put an async wrapper around the synchronous method, and just await it. However, I don't need to do that now that I can read from the basestream reliably.
Update: I now reliably receive more than 3Mbps from my serial port using a Task containing a loop that continuously awaits SerialPort.Basestream.ReadAsync and adds the results to a circular buffer.
I know it's quite some time since question was asked/solved, but noticed it while searching. I have had same kind of "problems" earlier. Nowadays I use a Pipereader over the BaseStream of the serial port to handle reading. This allows me to only clear the incoming buffers when I have a complete message (and receive several messages at the same time). And it seems to perform very well.
Code is something like this:
var reader = PipeReader.Create(serial.BaseStream);
while (!token.IsCancellationRequested)
{
ReadResult result = await reader.ReadAsync(token);
// find and handle packets
// Normally wrapped in a handle-method and a while to allow processing of several packets at once
// while(HandleIncoming(result))
// {
result.Buffer.Slice(10); // Moves Buffer.Start to position 10, which we use later to advance the reader
// }
// Tell the PipeReader how much of the buffer we have consumed. This will "free" that part of the buffer
reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
// Stop reading if there's no more data coming
if (result.IsCompleted)
{
break;
}
}
See the documentation for pipelines here: https://docs.microsoft.com/en-us/dotnet/standard/io/pipelines