I have a .Net Core 5 console app that runs on Linux (Debian 10). The basic structure is something like this:
class Program
{
static async Task Main(string[] args)
{
await SetupStuffAsync();
MonitorGpioService.Run();
RunAScheduledServiceOnATimer();
Console.ReadLine();
}
}
Basically, it runs on an Orange Pi Zero (similar to a Raspberry Pi), waiting for a signal on a GPIO pin. When that signal arrives, it reads the serial port for a few milliseconds, writes the data to a MariaDB database (using EF Core), and posts the data to a Web API. It also runs some scheduled maintenance code every 5 minutes (using System.Timers.Timer()).
This app runs unattended – there’s not even a screen – and must run always, from the moment the Orange Pi is booted up until it is shutdown.
Console.ReadLine() worked fine in stopping the app from ending when I was manually running the app during development.
But now I need the app to run automatically when Debian starts up, so I did the following:
sudo nano /etc/systemd/system/orangePi.service
[Unit]
Description=orangePi.service
[Service]
Type=simple
ExecStart=/root/dotnet/dotnet sr/local/bin/orangePiService/orangePiService.dll
Restart=on-failure
RestartSec=10
KillMode=process
[Install]
WantedBy=multi-user.target
This works great – the app starts up automatically on bootup, but there’s a problem. Console.ReadLine() is being completely ignored. It literally executes everything and then ends. I suppose this makes sense as it’s not running in the console in this case.
I know I can, for example, put an infinite loop at the end to prevent it from ending:
class Program
{
static async Task Main(string[] args)
{
await SetupStuffAsync();
MonitorGpioService.Run();
RunAScheduledServiceOnATimer();
while (0 == 0) {};
}
}
And this works, but I don’t like it. Not only is it not pretty, but also I would imagine that it’s using up a lot of CPU to run that loop.
I could do this instead:
class Program
{
static async Task Main(string[] args)
{
await SetupStuffAsync();
MonitorGpioService.Run();
RunAScheduledServiceOnATimer();
while (0 == 0)
{
Thread.Sleep(int.MaxValue);
};
}
}
Which I would imagine would be less taxing on the CPU, but I think it’s blocking this thread. Is that a problem? I know it depends on what the rest of the code looks like, but it’s quite a bit of code to post here. What I can say is that most of the action happens in MonitorGpioService.Run() which I am posting below in an extremely simplified format:
using System.Device.Gpio;
public static class MonitorGpioService()
{
static GpioController _controller;
public static void Run()
{
_controller = new GpioController();
_controller.RegisterCallbackForPinValueChangedEvent((int)Settings.GpioPin.Read, PinEventTypes.Rising, onSignalPinValueChangedEvent);
}
private static void onSignalPinValueChangedEvent(object sender, PinValueChangedEventArgs args)
{
string data = ReadSerialPortFor40ms();
using (var context = new eballContext())
{
await _dbContext.Readings.AddRangeAsync(readings);
await _dbContext.SaveChangesAsync();
}
}
}
I am using awaits wherever possible, which I think wouldn’t be affected by the main thread being blocked. I’m not sure about the firing of the event handler when a GPIO pin state changes though. From my testing, it doesn’t appear to be affected by blocking the main thread, but I can’t be sure…
In summary (and I apologize for the length of this post), I’m trying to figure out what’s the best way to prevent a .Net Core console app from quitting when running on Linux as a service. And by best, I mean one that consumes as little CPU as possible, or maybe blocks threads as little as possible (assuming this is even a problem to begin with, which I’m not really sure considering most of my code runs on Timers or uses awaits).
2
Answers
Well, while loop is a beautiful thing but it is enemy of the CPU.
Instead of while, I often use ManualResetEvent class to prevent closing console apps.
Usually it works when i use this code block in Linux containers on Docker.
I don’t have the actual code block, I am going to remember it like;
Basically the shutdown signal never comes and console app never closes. Of course you can develop more targeted code to your needs. This prevents console app shutting down while all that stuff works. You can give it a try. Also you can find lots of different apporaches in SO.
From the answers for C# console program wait forever for event
Task.Delay()
itself is typically more elegant is it allows you to pass a cancellation token, enabling graceful shutdowns if needed.Thread.Sleep()
should also work, but cannot be cancelled. Instead of a while loop you can useTimeout.Infinite
to suspend without wasting any cycles