How to write a linux daemon with .Net Core

后端 未结 5 1951
离开以前
离开以前 2021-01-31 05:22

I could just write a long-running CLI app and run it, but I\'m assuming it wouldn\'t comply to all the expectations one would have of a standards-compliant linux daemon (respond

相关标签:
5条回答
  • 2021-01-31 05:55

    Implementing Linux Daemon or service for windows quite easy with single codebase using Visual Studio 2019. Just create project using WorkerService template. In my case I have Coraval library to schedule the tasks.

    Program.cs class

    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 class

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

    For linux daemon use UseSystemd and for windows service use UseWindowsService as per the above code.

    0 讨论(0)
  • 2021-01-31 05:59

    Have you tried 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();
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-31 06:06

    If you're trying to find something more robust, I found an implementation on Github that looks promising: .NET Core Application blocks for message-based communication. It uses Host, HostBuilder, ApplicationServices, ApplicationEnvironment, etc classes to implement a messaging service.

    It doesn't quite look ready for black box reuse, but it seems like it could be a good starting point.

    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);
    };
    ...
    
    0 讨论(0)
  • 2021-01-31 06:08

    The best I could come up with is based on the answer to two other questions: Killing gracefully a .NET Core daemon running on Linux and Is 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);
            }
    
        }
    }
    
    0 讨论(0)
  • 2021-01-31 06:14

    I toyed with an idea similar to how .net core web host waits for shutdown in console applications. I was reviewing it on GitHub and was able to extract the gist of how they performed the 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;
        }
    }
    

    I tried adapting something like a IConsoleHost but quickly realized I was over-engineering it. Extracted the main parts into something like await ConsoleUtil.WaitForShutdownAsync(); that operated like Console.ReadLine

    This then allowed the utility to be used like this

    public class Program {
    
        public static async Task Main(string[] args) {
            //relevant code goes here
            //...
    
            //wait for application shutdown
            await ConsoleUtil.WaitForShutdownAsync();
        }
    }
    

    from there creating a systemd as in the following link should get you the rest of the way

    Writing a Linux daemon in C#

    0 讨论(0)
提交回复
热议问题