2017-01-04 2 views
11

Я мог бы просто написать долгосрочное приложение CLI и запустить его, но я предполагаю, что он не будет соответствовать всем ожиданиям, предъявляемым к стандарту, совместимому с Linux демон (отвечает на SIGTERM, начато процесс инициализации System V, Игнорировать сигналы ввода/вывода, etc.)Как написать демон linux с .Net Core

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

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

+0

Вопросы, касающиеся ресурсов за пределами площадки, таких как учебники, программное обеспечение и документация, не относятся к теме здесь, в SO. Я бы проголосовал за закрытие, но щедрость это предотвращает. – JNevill

+3

Большинство предположений, которые вы перечисляете, на самом деле не являются проблемой для современных систем. Менеджеры процессов, такие как systemd (которые используются в fedora/ubuntu/redhat/centos/arch/others), заботятся о работе в фоновом режиме и на самом деле лучше всего работают с программами, которые просто остаются на переднем плане и не пытаются ничего сделать с помощью fork() или сигналов. – larsks

+1

https://developers.redhat.com/blog/2017/06/07/writing-a-linux-daemon-in-c/ – Nkosi

ответ

10

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

Writing a Linux daemon in C#

+1

' async Main' - это язык C# 7.1. Если вы используете предыдущие версии, вы можете использовать 'static void Main' с' ConsoleUtil.Wait() 'или' ConsoleUtil.WaitForShutdown() '. – rianjs

+0

@rianjs это правильно. Вот почему я включил его в утилиту – Nkosi

+0

Да, ваш второй фрагмент кода ('public static async Task Main()') немного необычен, поэтому я специально назвал его наиболее распространенной альтернативой. На самом деле ваш ответ - это то, как я обнаружил, что есть C# 7.1 (!). – rianjs

1

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

Лучшее, что я мог придумать на основе ответа на два других вопроса: Killing gracefully a .NET Core daemon running on Linux и 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); 
     } 

    } 
} 
1

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

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

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); 
}; 
... 
Смежные вопросы