5

When I try to read from a serial port (uart) in Linux on an RaspberryPi I always get a CPU load of 5-10% when in a loop. As SerialPorts should be blocking, this shouldn't use that much cpu load, or am I wrong?

I tried two codes:

Simple Code

var port = new SerialPort("/dev/ttyUSB0", 57600);
port.Open();
while (true)
{
    if (port.BytesToRead > 0)
    {
    while (port.BytesToRead > 0)
        Console.Write($"{port.ReadByte().ToString("X2")} ");
    Console.WriteLine("");
    }
    Thread.Sleep(100);
}

Advanced Code

static int blockLimit = 100;
static void Main(string[] args)
{
    var port = new SerialPort("/dev/ttyUSB0", 57600);
    port.Open();
    byte[] buffer = new byte[blockLimit];
    Action kickoffRead = null;
    kickoffRead = delegate
    {
        port.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar)
        {
        try
        {
            int actualLength = port.BaseStream.EndRead(ar);
            byte[] received = new byte[actualLength];
            Buffer.BlockCopy(buffer, 0, received, 0, actualLength);
            raiseAppSerialDataEvent(received);
        }
        catch (IOException exc)
        {
            handleAppSerialError(exc);
        }
        kickoffRead();
        }, null);
    };
    kickoffRead();

    while (true)
        Thread.Sleep(1000);
}

private static void handleAppSerialError(IOException exc)
{
    throw new NotImplementedException();
}

private static void raiseAppSerialDataEvent(byte[] received)
{
    Console.WriteLine(BitConverter.ToString(received));
}

Both with the same result: Two processes which uses together 5% to 10% cpu load htop cpu%

Using .NET Core 3.0 Preview 2 and System.IO.Ports 4.6.0-preview-19073.11 on a RaspberryPi 3b+ running with HypriotOS 1.10.0

Max R.
  • 811
  • 1
  • 13
  • 31
  • 3
    I have the same problem. After migration from Mono to Net Core my cpu usage went from 5% to 25% Problem is that SerialStream implementation in NET Core uses async operations everywhere, even for synchronous Read/Write operations. see https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs Using async/await operations comes at the price. Thread context switching or creating SerialStreamIORequest objects isn't free. Solution is to use mono or try to use some other implementation. – Michal Dobrodenka Jan 30 '20 at 08:30
  • What library are you referencing? Where is the `SerialPort` class that you are referencing? I will like to have something similar – Tono Nam Sep 24 '20 at 20:01

2 Answers2

3

As for now (NET Core 3.1) SerialPort implementation is very CPU intensive. I've ported Mono SerialPort to Net Standard library according to dima117 response here .NET Core - Use System.IO.Ports.SerialPort in visual studio code

I've published it to github:

https://github.com/michaldobrodenka/System.IO.Ports.Mono

With this SerialPort implementation, CPU usage dropped from 25% to 5% on my allwinner h3 hw

Michal Dobrodenka
  • 1,104
  • 8
  • 27
1

I've had the same problem and did some profiling using Jetbrains's dotTrace. I got the following info:

7.71%   Poll  •  25,862/25,862 ms  •  Interop+Serial.Poll(SafeHandle, PollEvents, Int32, out PollEvents)
  7.71%   PollEvents  •  System.IO.Ports.SerialStream.PollEvents(Int32, Boolean, Boolean, out Nullable)
    7.71%   IOLoop  •  System.IO.Ports.SerialStream.IOLoop
      7.71%   InnerInvoke  •  System.Threading.Tasks.Task.InnerInvoke
         Thread #8

7 procent was about the same as I saw when running top -d 1.

Looking at the called methods I assume that polling is used, which is not very efficient. Especially if the polling interval is very short. So i took a look at the dotnet source code on GitHub and noticed that the interval is hard-coded to 1 ms (line 845).

Interop.PollEvents events = PollEvents(
    1,
    pollReadEvents: hasPendingReads,
    pollWriteEvents: hasPendingWrites,
    out Interop.ErrorInfo? error);

I'll see if I can create an issue at the GitHub repo.

Mr Balanikas
  • 1,657
  • 15
  • 28
  • There already is an [issue](https://github.com/dotnet/runtime/issues/2379), and it seems to be a big problem to solve – Peter Laudy Jan 08 '21 at 16:17