StreamReader.ReadLine() based on SerialPort.BaseStream is not working properly

EuroEager2008 171 Reputation points
2021-05-12T16:47:26.183+00:00

.Net 5

Due to massively reported problems using the SerialPort class (at least earlier and I don't know if problems have been fixed), e.g. ref Ben Voigt's article here: https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport, I have not used the SerialPort class for years except, as Ben recommends, grabbing the BaseStream property and forget about other property/methods.

So I wrote a program which is supposed to read textlines from a serialport, like:

            SerialPort port = new SerialPort("COM6", 9600, Parity.None, 7, StopBits.One);
            port.Open();
            StreamReader reader = new StreamReader(port.BaseStream);
            string inputString = reader.ReadLine();

Now, if I send the following bytes to the port, the reader.ReadLine() returns 'ABC' (as expected): 0x41, 0x42, 0x43, 0x0d, 0x0a
However, if I send this to the port, reader.ReadLine() doesn't return at all: 0x41, 0x42, 0x43, 0x0d (lacking linefeed)

I have tried to feed the latter bytes into a MemoryStream and a StreamReader on this works fine for ReadLine().
I also made a file with the latter bytes, StreamReader based on FileStream works fine.
In addition, ironically, the SerialPort.ReadLine() method seems to work fine (this is currently my fallback).

Why does StreamReader.ReadLine() not work properly when based on SerialPort.BaseStream?
(reading byte-by-byte with SerialPort's BaseStream ReadByte() method proves that the bytes fed to the stream is indeed the latter sequence).

I do not understand how the StreamReader.ReadLine() acts differently depending on kind of stream as long as e.g. BaseStream.ReadByte() works fine.

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
9,936 questions
.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,101 questions
{count} votes

4 answers

Sort by: Most helpful
  1. Cheong00 3,466 Reputation points
    2021-05-13T03:46:27.457+00:00

    I don't get what the problem is from you description. It is "by design" that StreamReader.ReadLine() has to wait for '\n', '\r' or "\r\n" to return. (Hence ReadLine())

    It works the same way no matter you use it to read from file or network stream.

    For your use case you're advised to use .ReadToEnd() instead.


  2. Cheong00 3,466 Reputation points
    2021-05-13T07:13:20.383+00:00

    I spent some time with the article you quote.

    While I agree that .BytesToRead is mostly useless as it can return more data when you read it, I've not noticed any problem when working with .DataReceived. (Some background. I don't write App for Embedded (IOT), but for Windows Desktop, I've been using System.IO.Ports.SerialPort to drain data from a lot of places like PABX, Door control security system, or RFID readers for production lines (operates 20+ hours a day) and had never experienced any complaint that it's not working.)

    Also, the preferred way to read serial port is like "general purpose read for file". I use a fixed sized buffer to read, and use the return value of SerialPort.Read() to determine the number of bytes to process.

    The complaint about Read methods are synchronous is puzzling because serial ports are, serial. There is no use to have multiple listener to serve a single port. The most common usage pattern is to have single worker process to perform read/write to each port you need to process, and as soon as it received data, delegate the work to ThreadPool task to process it, or put the data on queue (MQ if the processing model is chosen to be multi-process) and let multiple workers pick new data when done. I'm very curious on what kind of usage pattern he plan to write if asynchronous methods are added.

    0 comments No comments

  3. EuroEager2008 171 Reputation points
    2021-05-13T10:05:06.797+00:00

    Please consider the following naive program which you can start with the different arguments.
    On my computer I had COM5 and COM6 to play with, please cahnge according to your setup.
    Please connect a cable between the ports (Crossed wiring).

    This program is not the answer to your question.
    To answer your question: Use e.g. PuTTY and set it up with 9600, 7 databits, none parity, one stopbit (same as program).

    When using PuTTY a press on the eneter button generates only a 0x0d (CR), to generate a 0x0a (LF) you must press ctrl-j.

    Start the program with e.g. BytesFromStream as an argument (just to check what is really entering the port, byte for byte).
    Then start PuTTY and press the following sequence of buttons (commas excluded): A,B,C,<Enter>
    You will se the bytes shown in console (last byte 0x0d)
    Try also A,B,C,<Enter>,<ctrl-j> and observe that last 2 printouts are 0x0d and 0x0a

    Restart the program with argument LinesFromPort and try the same sequences as above.
    Lines are printed either they are terminated with 0x0d only or 0x0a only or 0x0d 0x0a

    Finally restart the program with argument LinesFromStreamReader and observe that terminating the stream with only 0x0d (CR) does not make the ReadLines returning.
    To make the StreamReader.ReadLine to return the line it must be terminated with both 0x0d and 0x0a (CRLF).
    This is as far as I can understand not according to the class documentation of StreamReader.

    If you modify the program by using a filestream instead of SerialStream (which is the internal class of SerialPort.BaseStream) and the file lacks the 0x0a (LF) a StreamReader on this stream will indeed return the line (according to documentation).
    StreamReader based on a MemoryStream behaves good as well, haven't tried a NetworkStream yet.

    Src:

    using System;
    using System.IO;
    using System.IO.Ports;

    namespace SerialPortTestConsole
    {
    class Program
    {
    static void Main(string[] args)
    {
    SerialPort port = new SerialPort("COM6", 9600, Parity.None, 7, StopBits.One);
    port.Open();
    port.DiscardInBuffer();
    port.DiscardOutBuffer();
    Stream stream = port.BaseStream;

            if (args.Length == 0 || args[0] == "LinesFromStreamReader")
            {
                StreamReader reader = new StreamReader(stream);
                while (true)
                {
                    Console.WriteLine($"Line received by StreamReader.ReadLine(): {reader.ReadLine()}");
                }
            }
            else if (args[0] == "BytesFromStream")
            {
                while (true)
                {
                    Console.WriteLine($"Byte read by Stream.ReadByte(): 0x{((byte)stream.ReadByte()):x2}");
                }
            }
            else if (args[0] == "LinesFromPort")
            {
                port.NewLine = "\r";
                while (true)
                {
                    Console.WriteLine($"Line received by Port.ReadLine(): {port.ReadLine()}");
                }
            }
            else Console.WriteLine("Wrong argument");
        }
    }
    

    }


  4. TireReturn 1 Reputation point
    2022-09-26T23:21:41.703+00:00

    Have you tried setting NewLine to \d instead of default \n ?

    0 comments No comments