web-dev-qa-db-de.com

Wie schreibt man einen Linux-Daemon mit .Net Core?

Ich könnte einfach eine langwierige CLI-App schreiben und ausführen, aber ich gehe davon aus, dass sie nicht alle Erwartungen erfüllen würde, die man von einem standardkonformen Linux-Dämon erwartet. Ignoriere die E/A-Signale der Terminals, usw. )

Für die meisten Ökosysteme gibt es hierfür einige bewährte Methoden. Beispielsweise können Sie in Python https://pypi.python.org/pypi/python-daemon/ verwenden.

Gibt es eine Dokumentation, wie dies mit .Net Core möglich ist?

17
Jordan Morris

Ich spielte mit einer Idee, die ähnlich ist, wie der .net Core Web Host auf das Herunterfahren von Konsolenanwendungen wartet. Ich habe es auf GitHub überprüft und konnte den Gist herausfinden, wie er die Variable Run ausgeführt hat.

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

Ich habe versucht, etwas wie eine IConsoleHost anzupassen, erkannte jedoch schnell, dass ich es überarbeitet hatte. Extrahierte die Hauptteile in etwas wie await ConsoleUtil.WaitForShutdownAsync();, das wie Console.ReadLine arbeitete

Dadurch konnte das Dienstprogramm auf diese Weise verwendet werden

public class Program {

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

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

von dort aus erstellen Sie ein systemd wie im folgenden Link, damit Sie den Rest des Weges bekommen

Einen Linux-Daemon in C # schreiben

22
Nkosi

Das Beste, was ich mir vorstellen kann, basiert auf der Antwort auf zwei weitere Fragen: Einen .NET Core-Dämon, der unter Linux ausgeführt wird, ordnungsgemäß beenden und Kann ein Ereignis statt einer anderen asynchronen Methode abgewartet werden?

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

Wenn Sie nach etwas Robusterem suchen, habe ich auf Github eine Implementierung gefunden, die vielversprechend aussieht: .NET Core-Anwendungsblöcke für nachrichtenbasierte Kommunikation . Es verwendet die Klassen Host, HostBuilder, ApplicationServices, ApplicationEnvironment usw., um einen Nachrichtendienst zu implementieren. 

Für die Black Box-Wiederverwendung sieht es noch nicht so aus, aber es scheint ein guter Ausgangspunkt zu sein. 

var Host = new HostBuilder()
            .ConfigureServices(services =>
            {
                var settings = new RabbitMQSettings { ServerName = "192.168.80.129", UserName = "admin", Password = "[email protected]" };
           })
            .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

Haben Sie Thread.Sleep (Timeout.Infinite) versucht?

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