web-dev-qa-db-ja.com

.Net Coreを使用してLinuxデーモンを作成する方法

実行時間の長いCLIアプリを作成して実行することもできますが、標準に準拠したLinuxデーモン(SIGTERMに対応し、System V initプロセスによって開始されます)のすべての期待に応えられないと想定しています。端末I/O信号を無視 etc。

ほとんどのエコシステムには、これを行うためのいくつかのベストプラクティスの方法があります。たとえば、Pythonでは https://pypi.python.org/pypi/python-daemon/ を使用できます。

.Net Coreでこれを行う方法に関するドキュメントはありますか?

18
Jordan Morris

.netコアWebホストがコンソールアプリケーションでシャットダウンを待機する方法に似たアイデアをもてあそびました。私はそれを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のようなものを適応させようとしましたが、すぐにそれを過剰設計していることに気付きました。主要な部分を_Console.ReadLine_のように動作するawait ConsoleUtil.WaitForShutdownAsync();のようなものに抽出しました

これにより、ユーティリティをこのように使用できるようになりました

_public class Program {

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

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

そこから、次のリンクのようにsystemdを作成すると、残りの方法が得られます

C#でLinuxデーモンを書き込む

25
Nkosi

私が思いつくことができる最高のものは、他の2つの質問への回答に基づいています: Linuxで実行されている.NET Coreデーモンを正常に終了します および 別の非同期の代わりにイベントを待つことは可能ですか?メソッド?

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

より堅牢なものを見つけようとしている場合、有望に見えるGithubの実装が見つかりました: メッセージベースの通信用の.NET Core Applicationブロック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);
};
...
2
Steve Clanton

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