利用Topshelf把.NET Core Generic Host管理的應用程序部署為Windows服務


背景

2019第一篇文章。

此文源於前公司在遷移項目到.NET Core的過程中,希望使用Generic Host來管理定時任務程序時,沒法部署到Windows服務的問題,而且官方也沒給出解決方案,只能關注一下官方issue #809 等他們方解決了。

官方文檔只提供了一個《在 Windows 服務中托管 ASP.NET Core》的方案,可以使用Microsoft.AspNetCore.Hosting.WindowsServices類庫來把Web應用部署為Windows服務。但是ASP.NET Core雖然是控制台程序,但是它本身是使用了含有HTTP管道的Web Host來負責應用程序的生命周期管理,用它來作為定時任務的話,會有很多不必要的工作負載,例如占用端口、增加了很多依賴等等。

官方意識到這個問題之后,在.NET Core 2.1版本新增了Generic Host通用主機,剝離了原來WebHost的Http管道相關的API,源碼中可以發現Web Host已經基於Generic Host實現。它才是作為純粹定時任務程序的最佳拍檔。

但是由於Generic Host本身非常簡單,用它運行的程序設置在注冊為Windows服務啟動之后會自動停止。研究很久之后才知道,想在Windows上啟動服務,還是不能像Linux上那么簡單——

於是嘗試結合Topshelf來創建Windows服務,最終成功了。

實現方法

  1. 先實現IHostLifetime接口來接管應用程序的生命周期,其實就是用空的實現來替換掉默認的ConsoleLifetime,這樣就可以在之后由Topshelf框架內部去管理生命周期。
    internal class TopshelfLifetime : IHostLifetime
    {
        public TopshelfLifetime(IApplicationLifetime applicationLifetime, IServiceProvider services)
        {
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
        }

        private IApplicationLifetime ApplicationLifetime { get; }

        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
  1. 然后實現IHostedService接口,把后台任務邏輯寫到StartAsync方法中,參見官方文檔《在 ASP.NET Core 中使用托管服務實現后台任務》,本文示例使用定時寫入文本到一個文件來測試定時任務是否成功運行。
    internal class FileWriterService : IHostedService, IDisposable
    {
        private static string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"test.txt");

        private Timer _timer;

        public Task StartAsync(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken);

            _timer = new Timer(
                (e) => WriteTimeToFile(),
                null,
                TimeSpan.Zero,
                TimeSpan.FromSeconds(10));

            return Task.CompletedTask;
        }

        public void WriteTimeToFile()
        {
            if (!File.Exists(path))
            {
                using (var sw = File.CreateText(path))
                {
                    sw.WriteLine(DateTime.Now);
                }
            }
            else
            {
                using (var sw = File.AppendText(path))
                {
                    sw.WriteLine(DateTime.Now);
                }
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
  1. 構建Generic Host,在ConfigureServices方法中注冊TopshelfLifetime,並且注冊一個托管服務FileWriterService,就能完成Generic Host的簡單構建,當然完整的項目應該還包含配置、日志等等。最后,使用Topshelf來接管Generic Host,創建Windows服務。
    internal class Program
    {
        private static void Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddSingleton<IHostLifetime, TopshelfLifetime>();
                    services.AddHostedService<FileWriterService>();
                });

            HostFactory.Run(x =>
            {
                x.SetServiceName("GenericHostWindowsServiceWithTopshelf");
                x.SetDisplayName("Topshelf創建的Generic Host服務");
                x.SetDescription("運行Topshelf創建的Generic Host服務");

                x.Service<IHost>(s =>
                {
                    s.ConstructUsing(() => builder.Build());
                    s.WhenStarted(service =>
                    {
                        service.Start();
                    });
                    s.WhenStopped(service =>
                    {
                        service.StopAsync();
                    });
                });
            });
        }
    }
  1. 最后發布應用程序,並安裝到Windows服務。

以管理員權限開啟終端,執行命令:

  dotnet publish -c release -r win-x64
  
  cd path-to-project/bin/release/netcoreapp2.1/win-x64/publish

  ./project-name install

  net start GenericHostWindowsServiceWithTopshelf

這樣這個Windows服務就啟動了!查看輸出文件,可以看到定時寫入成功,服務也一直沒關閉~

示例代碼

https://github.com/ElderJames/GenericHostWindowsServiceWithTopshelf

參考鏈接

官方文檔《.NET 通用主機》

官方文檔《在 ASP.NET Core 中使用托管服務實現后台任務》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM