web-dev-qa-db-fra.com

Comment écrire un démon Linux avec .Net Core

Je pourrais simplement écrire une application CLI de longue durée et l'exécuter, mais je suppose qu'elle ne répondrait pas à toutes les attentes d'un démon Linux conforme aux normes (répondant à SIGTERM, processus initié par System V, Ignorer les signaux d'E/S du terminal, etc. )

La plupart des écosystèmes ont ce qu’il est préférable de faire. Par exemple, en python, vous pouvez utiliser https://pypi.python.org/pypi/python-daemon/

Existe-t-il une documentation sur la manière de procéder avec .Net Core?

17
Jordan Morris

Je me suis amusé avec une idée similaire à la façon dont l'hôte Web principal .net attend l'arrêt des applications de la console. Je passais en revue cela sur GitHub et pouvais extraire le résumé de la façon dont ils ont exécuté le 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;
    }
}

J'ai essayé d'adapter quelque chose comme un IConsoleHost, mais j'ai vite compris que je le surenginonnais. Extraire les parties principales en quelque chose comme await ConsoleUtil.WaitForShutdownAsync(); qui fonctionnait comme Console.ReadLine

Cela a ensuite permis à l'utilitaire d'être utilisé comme ceci

public class Program {

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

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

à partir de là, créer un systemd comme dans le lien suivant devrait vous permettre d'obtenir le reste du chemin

Ecriture d'un démon Linux en C #

22
Nkosi

Le mieux que je puisse trouver est basé sur la réponse à deux autres questions: Tuer gracieusement un démon .NET Core s'exécutant sous Linux et Est-il possible d'attendre un événement à la place d'une autre méthode async?

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
Steve Clanton

Si vous essayez de trouver quelque chose de plus robuste, j'ai trouvé une implémentation prometteuse sur Github: Blocages .NET Core Application pour la communication basée sur des messages . Il utilise les classes Host, HostBuilder, ApplicationServices, ApplicationEnvironment, etc. pour mettre en œuvre un service de messagerie. 

Cela ne semble pas tout à fait prêt pour la réutilisation de la boîte noire, mais cela semble être un bon point de départ. 

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);
};
...
2
Steve Clanton

Avez-vous essayé 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();
        }
    }
}
1
MarineHero