防止 .Net Core 5 控制台应用程序在作为服务在 Linux 上运行时结束的推荐方法

发布时间:2021-03-07 13:47

我有一个在 Linux (Debian 10) 上运行的 .Net Core 5 控制台应用程序。基本结构是这样的:

class Program
{
    static async Task Main(string[] args)
    {
        await SetupStuffAsync();
        MonitorGpioService.Run();
        RunAScheduledServiceOnATimer();
        Console.ReadLine();
    }
}

基本上,它在 Orange Pi Zero(类似于 Raspberry Pi)上运行,等待 GPIO 引脚上的信号。当该信号到达时,它会读取串行端口几毫秒,将数据写入 MariaDB 数据库(使用 EF Core),并将数据发布到 Web API。它还每 5 分钟运行一些定期维护代码(使用 System.Timers.Timer())。

此应用程序在无人看管的情况下运行 - 甚至没有屏幕 - 并且必须始终运行,从 Orange Pi 启动到关闭。

Console.ReadLine() 在我在开发过程中手动运行应用程序时可以很好地阻止应用程序结束。 但是现在我需要在 Debian 启动时自动运行该应用程序,因此我执行了以下操作:

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

这很好用 - 应用程序在启动时自动启动,但存在问题。 Console.ReadLine() 被完全忽略。它从字面上执行一切,然后结束。我想这是有道理的,因为在这种情况下它不在控制台中运行。

我知道我可以,例如,在末尾放置一个无限循环以防止它结束:

class Program
{
    static async Task Main(string[] args)
    {
        await SetupStuffAsync();
        MonitorGpioService.Run();
        RunAScheduledServiceOnATimer();
        while (0 == 0) {};
    }
}

这行得通,但我不喜欢。它不仅不漂亮,而且我认为它会占用大量 CPU 来运行该循环。

我可以这样做:

class Program
{
    static async Task Main(string[] args)
    {
        await SetupStuffAsync();
        MonitorGpioService.Run();
        RunAScheduledServiceOnATimer();
        while (0 == 0)
        {
            Thread.Sleep(int.MaxValue);
        };
    }
}

我认为这会减少 CPU 的负担,但我认为它阻塞了这个线程。那是问题吗?我知道这取决于代码的其余部分是什么样的,但在这里发布的代码相当多。我可以说的是,大部分操作发生在 MonitorGpioService.Run() 中,我以极其简化的格式在下面发布:

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();
        }
    }    
}    

我尽可能使用等待,我认为这不会受到主线程被阻塞的影响。我不确定当 GPIO 引脚状态改变时事件处理程序的触发。从我的测试来看,它似乎不受阻塞主线程的影响,但我不能确定...

总而言之(对于这篇文章的长度,我深表歉意),我试图找出在 Linux 作为服务运行时防止 .Net Core 控制台应用程序退出的最佳方法是什么。最好的情况是,我的意思是尽可能少地消耗 CPU,或者尽可能少地阻塞线程(假设这甚至是一个问题,我不确定考虑到我的大部分代码都在计时器上运行或使用等待)。

回答1

好吧,while 循环是一件美妙的事情,但它是 CPU 的敌人。 而不是 while,我经常使用 ManualResetEvent 类来防止关闭控制台应用程序。 通常,当我在 Docker 上的 Linux 容器中使用此代码块时,它会起作用。 我没有实际的代码块,我会记住它;

        public static ManualResetEvent _Shutdown = new ManualResetEvent(false);
        static void Main(string[] args)
        {
           //Lots of stuff.
            _Shutdown.WaitOne();

        }

基本上,关机信号永远不会出现,控制台应用程序也永远不会关闭。当然,您可以根据自己的需要开发更有针对性的代码。这可以防止控制台应用程序在所有这些工作正常时关闭。你可以试一试。您还可以在 SO 中找到许多不同的方法。

回答2

来自C# console program wait forever for event的答案

<块引用>

在异步 main 方法的情况下,也可以使用 await Task.Delay(-1);

Task.Delay() 本身通常更优雅,因为它允许您传递取消令牌,在需要时启用正常关闭。

Thread.Sleep() 也应该有效,但不能取消。您可以使用 Timeout.Infinite 来暂停而不浪费任何周期