【问题标题】:How to write a linux daemon with .Net Core如何使用 .Net Core 编写 linux 守护进程
【发布时间】:2017-05-18 04:44:39
【问题描述】:

我可以编写一个长时间运行的 CLI 应用程序并运行它,但我假设它不符合人们对符合标准的 linux 守护程序的所有期望(响应 SIGTERM,由 System V 启动init 进程,忽略终端 I/O 信号,etc.)

大多数生态系统都有一些最佳实践方法,例如,在 python 中,您可以使用https://pypi.python.org/pypi/python-daemon/

是否有一些关于如何使用 .Net Core 执行此操作的文档?

【问题讨论】:

  • 寻求非现场资源(如教程、软件和文档)的问题不在 SO 中。我会投票关闭,但赏金正在阻止它。
  • 您列出的大多数假设对于现代系统来说并不是真正的问题。像systemd这样的进程管理器(在fedora/ubuntu/redhat/centos/arch/others上使用)负责在后台运行东西,实际上最适合那些只停留在前台并且不尝试做任何事情的程序喜欢 fork() 或信号。
  • 你能在minimal reproducible example中展示你到目前为止所做的尝试
  • 你在使用异步 Main 吗?

标签: linux asp.net-core .net-core daemon


【解决方案1】:

我玩弄了一个类似于 .net 核心网络主机如何在控制台应用程序中等待关闭的想法。我在 GitHub 上对其进行了审查,并能够提取他们如何执行 Run

的要点

https://github.com/aspnet/Hosting/blob/15008b0b7fcb54235a9de3ab844c066aaf42ea44/src/Microsoft.AspNetCore.Hosting/WebHostExtensions.cs#L86

public static class ConsoleHost {
    /// <summary>
    /// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM.
    /// </summary>
    public static void WaitForShutdown() {
        WaitForShutdownAsync().GetAwaiter().GetResult();
    }


    /// <summary>
    /// Runs an application and block the calling thread until host shutdown.
    /// </summary>
    /// <param name="host">The <see cref="IWebHost"/> to run.</param>
    public static void Wait() {
        WaitAsync().GetAwaiter().GetResult();
    }

    /// <summary>
    /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered.
    /// </summary>
    /// <param name="host">The <see cref="IConsoleHost"/> to run.</param>
    /// <param name="token">The token to trigger shutdown.</param>
    public static async Task WaitAsync(CancellationToken token = default(CancellationToken)) {
        //Wait for the token shutdown if it can be cancelled
        if (token.CanBeCanceled) {
            await WaitAsync(token, shutdownMessage: null);
            return;
        }
        //If token cannot be cancelled, attach Ctrl+C and SIGTERN shutdown
        var done = new ManualResetEventSlim(false);
        using (var cts = new CancellationTokenSource()) {
            AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: "Application is shutting down...");
            await WaitAsync(cts.Token, "Application running. Press Ctrl+C to shut down.");
            done.Set();
        }
    }

    /// <summary>
    /// Returns a Task that completes when shutdown is triggered via the given token, Ctrl+C or SIGTERM.
    /// </summary>
    /// <param name="token">The token to trigger shutdown.</param>
    public static async Task WaitForShutdownAsync(CancellationToken token = default (CancellationToken)) {
        var done = new ManualResetEventSlim(false);
        using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) {
            AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: string.Empty);
            await WaitForTokenShutdownAsync(cts.Token);
            done.Set();
        }
    }

    private static async Task WaitAsync(CancellationToken token, string shutdownMessage) {
        if (!string.IsNullOrEmpty(shutdownMessage)) {
            Console.WriteLine(shutdownMessage);
        }
        await WaitForTokenShutdownAsync(token);
    }


    private static void AttachCtrlcSigtermShutdown(CancellationTokenSource cts, ManualResetEventSlim resetEvent, string shutdownMessage) {
        Action ShutDown = () => {
            if (!cts.IsCancellationRequested) {
                if (!string.IsNullOrWhiteSpace(shutdownMessage)) {
                    Console.WriteLine(shutdownMessage);
                }
                try {
                    cts.Cancel();
                } catch (ObjectDisposedException) { }
            }
            //Wait on the given reset event
            resetEvent.Wait();
        };

        AppDomain.CurrentDomain.ProcessExit += delegate { ShutDown(); };
        Console.CancelKeyPress += (sender, eventArgs) => {
            ShutDown();
            //Don't terminate the process immediately, wait for the Main thread to exit gracefully.
            eventArgs.Cancel = true;
        };
    }

    private static async Task WaitForTokenShutdownAsync(CancellationToken token) {
        var waitForStop = new TaskCompletionSource<object>();
        token.Register(obj => {
            var tcs = (TaskCompletionSource<object>)obj;
            tcs.TrySetResult(null);
        }, waitForStop);
        await waitForStop.Task;
    }
}

我尝试改写 IConsoleHost 之类的东西,但很快意识到我过度设计了它。将主要部分提取为类似于await ConsoleUtil.WaitForShutdownAsync(); 的东西,其操作类似于Console.ReadLine

这样就可以像这样使用该实用程序

public class Program {

    public static async Task Main(string[] args) {
        //relevant code goes here
        //...

        //wait for application shutdown
        await ConsoleUtil.WaitForShutdownAsync();
    }
}

从那里创建一个 systemd,如以下链接所示,应该可以帮助您完成剩下的工作

Writing a Linux daemon in C#

【讨论】:

  • async Main 是 C# 7.1 语言功能。如果您使用的是以前的版本,您可以将static void MainConsoleUtil.Wait()ConsoleUtil.WaitForShutdown() 一起使用。
  • @rianjs 是正确的。这就是为什么我将它包含在 util
  • 是的,您的第二个代码 sn-p (public static async Task Main()) 有点不寻常,所以我特意将其称为更常见的替代方案。您的回答实际上是我如何发现有一个 C# 7.1(!)。
【解决方案2】:

我能想到的最佳答案是基于对另外两个问题的回答:Killing gracefully a .NET Core daemon running on LinuxIs it possible to await an event instead of another async method?

using System;
using System.Runtime.Loader;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    public class Program
    {
        private static TaskCompletionSource<object> taskToWait;

        public static void Main(string[] args)
        {
            taskToWait = new TaskCompletionSource<object>();

            AssemblyLoadContext.Default.Unloading += SigTermEventHandler;
            Console.CancelKeyPress += new ConsoleCancelEventHandler(CancelHandler);

            //eventSource.Subscribe(eventSink) or something...

            taskToWait.Task.Wait();

            AssemblyLoadContext.Default.Unloading -= SigTermEventHandler;
            Console.CancelKeyPress -= new ConsoleCancelEventHandler(CancelHandler);

        }


        private static void SigTermEventHandler(AssemblyLoadContext obj)
        {
            System.Console.WriteLine("Unloading...");
            taskToWait.TrySetResult(null);
        }

        private static void CancelHandler(object sender, ConsoleCancelEventArgs e)
        {
            System.Console.WriteLine("Exiting...");
            taskToWait.TrySetResult(null);
        }

    }
}

【讨论】:

    【解决方案3】:

    如果你想找到更健壮的东西,我在 Github 上找到了一个看起来很有希望的实现:.NET Core Application blocks for message-based communication。它使用HostHostBuilderApplicationServicesApplicationEnvironment等类来实现消息服务。

    它看起来还没有为黑盒重用做好准备,但它似乎是一个很好的起点。

    var host = new HostBuilder()
                .ConfigureServices(services =>
                {
                    var settings = new RabbitMQSettings { ServerName = "192.168.80.129", UserName = "admin", Password = "Pass@word1" };
               })
                .Build();
    
    Console.WriteLine("Starting...");
    await host.StartAsync();
    
    var messenger = host.Services.GetRequiredService<IRabbitMQMessenger>();
    
    Console.WriteLine("Running. Type text and press ENTER to send a message.");
    
    Console.CancelKeyPress += async (sender, e) =>
    {
        Console.WriteLine("Shutting down...");
        await host.StopAsync(new CancellationTokenSource(3000).Token);
        Environment.Exit(0);
    };
    ...
    

    【讨论】:

      【解决方案4】:

      使用 Visual Studio 2019 使用单个代码库非常容易为 windows 实施 Linux 守护程序或服务。只需使用 WorkerService 模板创建项目即可。就我而言,我有 Coraval library 来安排任务。

      Program.cs

      public class Program
      {
          public static void Main(string[] args)
          {
              Log.Logger = new LoggerConfiguration()
                      .MinimumLevel.Debug()
                      .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                      .Enrich.FromLogContext()
                      .WriteTo.File(@"C:\temp\Workerservice\logfile.txt").CreateLogger();
      
              IHost host = CreateHostBuilder(args).Build();
      
              host.Services.UseScheduler(scheduler =>
              {
      
                  scheduler
                    .Schedule<ReprocessInvocable>()
                    .EveryThirtySeconds();
              });
              host.Run();
          }
          public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args).UseSystemd() //.UseWindowsService()
      
              .ConfigureServices(services =>
              {
                  services.AddScheduler();
                  services.AddTransient<ReprocessInvocable>();
              });
      }
      

      ReprocessInvocable.cs

      public class ReprocessInvocable : IInvocable
      {
          private readonly ILogger<ReprocessInvocable> _logger;
          public ReprocessInvocable(ILogger<ReprocessInvocable> logger)
          {
              _logger = logger;
          }
          public async Task Invoke()
          {
              //your code goes here
              _logger.LogInformation("Information - Worker running at: {time}", DateTimeOffset.Now);
              _logger.LogWarning("Warning - Worker running at: {time}", DateTimeOffset.Now);
              _logger.LogCritical("Critical - Worker running at: {time}", DateTimeOffset.Now);
              Log.Information("Invoke has called at: {time}", DateTimeOffset.Now);
          }
      }
      

      对于linux daemon,使用UseSystemd,对于windows service,使用UseWindowsService,按照上述代码。

      【讨论】:

        【解决方案5】:

        您是否尝试过Thread.Sleep (Timeout.Infinite)

        using System;
        using System.IO;
        using System.Threading;
        
        namespace Daemon {
            class Program {
                static int Main(string[] args) {
                    if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
                        Log.Critical("Windows is not supported!");
                        return 1;
                    }
                    Agent.Init();
                    Agent.Start();
                    if (Agent.Settings.DaemonMode || args.FirstOrDefault() == "daemon") {
                        Log.Info("Daemon started.");
                        Thread.Sleep(Timeout.Infinite);
                    }
                    Agent.Stop();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2023-03-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-06
          • 2010-11-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多