.NET Core Generic Host Windows服務部署使用Topshelf
此文源於前公司在遷移項目到.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|1實現方法
- 先實現
IHostLifetime
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;
}
}
- 然後實現
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();
}
}
- 構建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();
});
});
});
}
}
- 最後發布應用程序,並安裝到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服務就啟動了!查看輸出文件,可以看到定時寫入成功,服務也一直沒關閉~
1|2示例代碼
https://github.com/ElderJames/GenericHostWindowsServiceWithTopshelf
1|3參考鏈接
官方文檔《.NET 通用主機》
官方文檔《在 ASP.NET Core 中使用托管服務實現後臺任務》
__EOF__
作 者:ElderJames
.NET Core Generic Host Windows服務部署使用Topshelf