0

I need some help reading data from a serial port connected to a device with an RS-232 interface. I decided to use an async/await style since there will be data constantly updating in a GUI, which should remain user-responsive.

The code below is the essence of my current implementation.

private async void MyReadFromSerialPortMethod()
{
    // (this.serialPort is a System.IO.Ports.SerialPort instance.)
    this.serialPort.DiscardInBuffer();
    this.serialPort.DiscardOutBuffer();

    this.serialPort.Write(/* bytes for "please send me some data now", 0, length */);

    byte[] header = new byte[3];
    await this.serialPort.BaseStream.ReadAsync(header, 0, header.Length);

    // do something with the header info first,
    // like check what kind of data is incoming

    byte[] data = new byte[5];
    await this.serialPort.BaseStream.ReadAsync(data, 0, data.Length);

    // do something with the data,
    // like show it to the user eventually
}

My issue is that, in the above code, the ReadAsync calls usually only read one byte from the input and then return from await (not always, but usually). If I were using synchronous methods, I could arrange for ReceivedBytesThreshold to contain the requisite number of bytes, but don't want to hold the UI thread while I do that. As far as I know, there is no such threshold for ReadAsync to delay returning. This would be very helpful.

I have implemented the following as a workaround, but it doesn't feel like a good solution to me. Looping around the asynchronous method feels like I'm just writing a busy-waiting loop to wait for the input buffer to fill, and I might as well use the synchronous version. While I know the await probably does return control to the caller to some extent between each byte arriving, this is not a question about profiling code for speed and efficiency, but rather about what the right coding pattern is and if I am taking advantage of asynchronous programming or handicapping it.

    byte[] header = new byte[3];
    int bytesRead = 0;
    while (bytesRead < header.Length)
    {
        bytesRead += await this.serialPort.BaseStream.ReadAsync(header, bytesRead, header.Length-bytesRead);
    }
    // similarly for byte[] data...
nivk
  • 685
  • 1
  • 6
  • 21
  • Your code is perfect, it won't eat CPU time and its pretty efficient. how ever you may want to consider cancelling the operation, use `CancellationToken` and throw if cancel is requested by user. put `token.ThrowIfCancellationRequested();` inside the loop – M.kazem Akhgary Mar 12 '19 at 07:07
  • other solution would be to use `Task.Run` to run synchronous code on another thread. – M.kazem Akhgary Mar 12 '19 at 07:11
  • You can run a separate thread for `read` and `write` simultaneously on the `BaseStream`. Or Continuously to `read` in a separate thread with `while(true)`, and write normally. – Pierre Mar 12 '19 at 07:25
  • @M.kazemAkhgary, thank you for your suggestion, I was considering something like this. I was also considering that this approach might require its own `Timeout` counter in case the device stops sending bytes permanently for some reason. Also with regard to @Pierre 's comment, I am not sure that I want to make this application explicitly multi-threaded. The advantages seem clear, but I am worried about potential disadvantages. – nivk Mar 12 '19 at 22:29

1 Answers1

1

In order to get a multi byte response you need a loop anyway because SerialPort.ReceivedBytesThreshold property is only relevant to SerialPort.DataReceived event. But probably in request-response scenario you still need to use synchronous API and Task.Run() because asynchronous API ignores SerialPort timeout properties completely and CancellationToken almost completely. That means that asynchronous SerialPort operations can hang forever holding buffers and possibly locks.

Also you need to synchronize access to the critical sections (logical blocks of interaction with a serial port), so multiple requests don't intervene with each other (using lock keyword, SemaphoreSlim instance or similar).

For example implementation please check C# await event and timeout in serial port communication discussion on StackOverflow.

In general you need to perform relevant tests and see if an approach leads to good user experience.

For more information check:

Leonid Vasilev
  • 11,910
  • 4
  • 36
  • 50
  • Thank you for this thoughtful answer and the links, very much appreciated. I will study them. I also found the following helpful: [If you must use .NET System.IO.Ports.SerialPort](http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport). Can you elaborate on the `Semaphore` usage? My understanding is that semaphores manage multiple-thread access, which doesn't apply when using `await`. It would only apply when using a separate `Task.Run()`, is that right? I agree that I should do more testing, in the end this determines the necessary approach. – nivk Mar 12 '19 at 22:29
  • That is a very good question. I had following scenario in mind: imagine that you send a request A to some device, UI thread is now free, device ignores request A because it's software version doesn't support it, asynchronous request A is still in process, you send request B to the device, the device answers to request B because it is a valid request and now UI thread processes response to B as if it is response to A. `Stream` has some internal synchronization mechanism but the exact `SerialStream` behavior is unclear to me. So I would organize critical sections on my side to be sure. – Leonid Vasilev Mar 15 '19 at 20:31
  • Research led me to a discovery regarding synchronous vs asynchronous API, please check the update. – Leonid Vasilev Mar 15 '19 at 20:33