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).