Как написать демон Linux с .Net Core

Я мог бы просто написать долго работающее приложение CLI и запустить его, но я предполагаю, что оно не будет соответствовать всем ожиданиям, которые можно было бы иметь от совместимого со стандартами демона Linux (отвечая на SIGTERM, запущенный процессом инициализации System V, Игнорировать сигналы ввода-вывода терминала, и т. д.)

В большинстве экосистем есть лучшие способы сделать это, например, в python вы можете использовать https://pypi.python.org/pypi/python-daemon/

Есть ли документация о том, как это сделать с .Net Core?


person Jordan Morris    schedule 04.01.2017    source источник
comment
Вопросы, связанные с поиском внешних ресурсов, таких как учебные пособия, программное обеспечение и документация, здесь, в SO, не относятся к теме. Я бы проголосовал за закрытие, но щедрость мешает этому.   -  person JNevill    schedule 19.09.2017
comment
Большинство предположений, которые вы перечисляете, на самом деле не беспокоят современные системы. Менеджеры процессов, такие как systemd (который используется в fedora/ubuntu/redhat/centos/arch/других), заботятся о работе в фоновом режиме и на самом деле лучше всего работают с программами, которые просто остаются на переднем плане и не пытаются ничего делать. фантазии с fork() или сигналами.   -  person larsks    schedule 19.09.2017
comment
developers.redhat.com/blog /2017/06/07/   -  person Nkosi    schedule 21.09.2017
comment
Можете ли вы показать, что вы уже пробовали, на минимально воспроизводимом примере   -  person Nkosi    schedule 21.09.2017
comment
Вы используете асинхронный Main?   -  person Nkosi    schedule 21.09.2017
comment
@SteveClanton Только что понял, что награда не была поднята ОП. Удовлетворила ли предоставленная ссылка ваш запрос?   -  person Nkosi    schedule 21.09.2017
comment
@Nkosi Могу ли я добавить MCVE к вопросу? Кажется, что это будет перешагнуть через редактирование. Оглядываясь назад, я должен был написать новую версию вопроса, но это казалось именно тем, что я хотел сделать, когда читал его. Честно говоря, я не знал, что вы можете сделать асинхронную основную. Я подумал о том, чтобы опубликовать то, что я сделал (создание задачи и окончание, которое длится до sigkill) в качестве ответа и посмотреть, не вдохновит ли это что-то лучшее.   -  person Steve Clanton    schedule 21.09.2017
comment
@JNevil Это не запрашивает сторонний ресурс. Он просит что-то, что должно быть задокументировано как часть структуры и, похоже, не является ИМХО.   -  person Steve Clanton    schedule 21.09.2017
comment
@SteveClanton Я поиграл с идеей, похожей на то, как веб-хост .net core ожидает завершения работы в консольных приложениях, но хотел посмотреть, впишется ли она в то, что у вас есть до сих пор. оттуда создание systemd, как в ссылке, должно помочь вам до конца   -  person Nkosi    schedule 21.09.2017
comment
@Nkosi Я подумал, что другие фреймворки в .Net работают в фоновом режиме. Если вы можете опубликовать, как веб-хост ожидает выключения, это, вероятно, будет лучшим из того, что есть у сообщества.   -  person Steve Clanton    schedule 21.09.2017
comment
@SteveClanton Я просматривал его на GitHub и смог извлечь суть того, как они выполняли ожидание github.com/aspnet/Hosting/blob/   -  person Nkosi    schedule 21.09.2017
comment
@SteveClanton Я пытался адаптировать что-то вроде IConsoleHost, но быстро понял, что перестарался. Извлек основные части во что-то вроде await ConsoleUtil.WaitForShutdownAsync();, которое работало как Console.ReadLine   -  person Nkosi    schedule 21.09.2017


Ответы (5)


Я играл с идеей, похожей на то, как веб-хост .net core ожидает завершения работы в консольных приложениях. Я просматривал его на 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, как в следующей ссылке, должно помочь вам до конца

Написание демона Linux на C#

person Nkosi    schedule 25.09.2017
comment
async Main — это функция языка C# 7.1. Если вы используете предыдущие версии, вы можете использовать static void Main с ConsoleUtil.Wait() или ConsoleUtil.WaitForShutdown(). - person rianjs; 29.10.2017
comment
@rianjs, это правильно. Вот почему я включил его в утилиту - person Nkosi; 29.10.2017
comment
Да, ваш второй фрагмент кода (public static async Task Main()) немного необычен, поэтому я специально назвал его более распространенной альтернативой. На самом деле ваш ответ - это то, как я обнаружил, что существует C # 7.1 (!). - person rianjs; 30.10.2017

Лучшее, что я смог придумать, основано на ответе на два других вопроса: linux">Изящное уничтожение демона .NET Core, работающего в Linux, и Можно ли ожидать события вместо другого асинхронного метода?

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

    }
}
person Steve Clanton    schedule 25.09.2017

Если вы ищете что-то более надежное, я нашел многообещающую реализацию на Github: .NET Базовые блоки приложений для обмена сообщениями. Он использует классы Host, HostBuilder, ApplicationServices, ApplicationEnvironment и т. д. для реализации службы обмена сообщениями.

Он не совсем готов к повторному использованию черного ящика, но кажется, что он может стать хорошей отправной точкой.

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);
};
...
person Steve Clanton    schedule 25.09.2017

Внедрение демона или службы Linux для windows довольно просто с единой базой кода с использованием Visual Studio 2019. Просто создайте проект, используя шаблон WorkerService. В моем случае у меня есть библиотека Coraval для планирования задач.

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 в соответствии с приведенным выше кодом.

person R15    schedule 08.06.2020

Вы пробовали 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();
        }
    }
}
person MarineHero    schedule 25.09.2017