導航
- 開始創建worker service 項目
- Program.cs
- Worker.cs
- 依賴注入(DI)
- 重寫BackgroundService類的StartAsync、ExecuteAsync、StopAsync方法
- 不要讓線程阻塞worker類中重寫的StartAsync、ExecuteAsync、StopAsync方法
- 在Worker Service中運行多個Worker類
- 部署為Windows服務運行
- 部署作為Linux守護程序運行
- 參考文獻
.NET Core 3.0新增了Worker Service的新項目模板,可以編寫長時間運行的后台服務,並且能輕松的部署成windows服務或linux守護程序。如果安裝的vs2019是中文版本,Worker Service的項目名稱就變成了輔助角色服務。Worker Service 咱也不知道怎么翻譯成了這個名稱,咱也不敢亂翻譯,下文就保持原名稱。。。,本文將會演示如何創建一個Worker Service項目,並且部署為windows服務或linux守護程序運行;
創建新項目——》選擇 Worker Service(輔助角色服務)
取一個項目名稱:DemoWorkerService
項目創建成功之后,您會看到創建了兩個類:Program和Worker。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace DemoWorkerService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); }); } }
Program類跟ASP.NET Core Web應用程序非常類似,不同之處沒有了startup類,並且把worker服務添加到DI container中。
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWorkerService { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } } }
worker只是一個簡單的類,它繼承自BackgroundService ,而后者又實現IHostedService接口。
我們可以在Program類中的ConfigureServices方法中,配置Worker類構造函數要用到的依賴注入關系,假如我們現在有IContainer接口和MyContainer類,它們是一組依賴注入關系:
public interface IContainer { string ContainerName { get; set; } } public class MyContainer : IContainer { protected string containerName = "Custom my container"; public string ContainerName { get { return containerName; } set { containerName = value; } } }
我們可以在Program類中的ConfigureServices方法中,使用services參數(該參數是IServiceCollection接口的對象)來配置IContainer接口和MyContainer類的依賴注入關系:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DemoWorkerService.Model; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace DemoWorkerService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddTransient<IContainer, MyContainer>();//配置IContainer接口和MyContainer類的依賴注入關系 services.AddHostedService<Worker>(); }); } }
然后在Worker類的構造函數中,Worker Service就會使用DI(依賴注入)自動注入IContainer類型的參數:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using DemoWorkerService.Model; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWorkerService { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; private readonly IContainer _container; //Worker Service會自動依賴注入Worker構造函數的IContainer container參數 public Worker(ILogger<Worker> logger, IContainer container) { _logger = logger; _container = container; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } } }
注意上面worker類的構造函數中,使用了.NET Core自帶的日志組件接口對象ILogger,它也是通過DI(依賴注入)注入到worker類的構造函數中的,和ASP.NET Core中Controller的構造函數類似(關於ILogger,可以查看這里了解),我們也可以不用日志組件,給worker類定義無參的默認構造函數。
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; namespace DemoWorkerService { public class Worker : BackgroundService { public Worker() { } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await Task.Delay(1000, stoppingToken); } } } }
重寫BackgroundService類的StartAsync、ExecuteAsync、StopAsync方法
我們可以通過 override ExecuteAsync 方法來完成自己要做的事情,該方法實際上屬於BackgroundService類,我們是在worker類中重寫(override)了它,這個方法中我們就可以調用在windows服務或linux守護程序中要處理的邏輯,原則上來說這個邏輯應該是在一個死循環中,並且通過ExecuteAsync方法傳入的CancellationToken參數對象,來判斷是否應該結束循環,例如如果windows服務被停止,那么參數中CancellationToken類的IsCancellationRequested屬性會返回true,那么我們應該停止ExecuteAsync方法中的循環,來結束整個windows服務:
//重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { //模擬服務中的處理邏輯,這里我們僅輸出一條日志,並且等待1秒鍾時間 _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } }
此外我們也可以在worker類中重寫BackgroundService.StartAsync方法和BackgroundService.StopAsync方法(注意重寫時,不要忘記在worker類中調用base.StartAsync和base.StopAsync,因為BackgroundService類的StartAsync和StopAsync會執行一些Worker Service的核心代碼),在開始和結束Worker Service服務(例如,開始和停止windows服務)的時候,來執行一些處理邏輯,本例中我們分別輸出了一條日志:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWorkerService { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } //重寫BackgroundService.StartAsync方法,在開始服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now); await base.StartAsync(cancellationToken); } //重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { //模擬服務中的處理邏輯,這里我們僅輸出一條日志,並且等待1秒鍾時間 _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } //重寫BackgroundService.StopAsync方法,在結束服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now); await base.StopAsync(cancellationToken); } } }
由於BackgroundService類的StartAsync、ExecuteAsync、StopAsync方法返回的都是Task類型,所以如同上面代碼所示,我們可以使用async和await關鍵字將它們重寫為異步函數,來提高程序的性能。現在我們在Visual Studio中直接運行上面的代碼,結果如下所示,每隔1秒,循環打印運行的時間:
其中我用三個紅色框,將worker類中重寫StartAsync、ExecuteAsync、StopAsync方法的輸出結果標識了出來,在Visual Studio中運行Worker Service項目時,可以在啟動的控制台中使用快捷鍵"Ctrl+C"來停止Worker Service的運行(相當於停止windows服務或linux守護程序),所以這樣我們可以在上面的結果中看到StartAsync、ExecuteAsync、StopAsync三個方法都被執行了,並且都輸出了日志。
其實我們可以看到Worker Service項目從本質上來說就是一個控制台項目,只不過當它被部署為windows服務或linux守護程序后,不會顯示控制台窗口。
所以實際上在Visual Studio中進行調試的時候,完全可以用Console.WriteLine等控制台方法來替代ILogger接口的日志輸出方法:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWorkerService { public class Worker : BackgroundService { public Worker() { } //重寫BackgroundService.StartAsync方法,在開始服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("Worker starting at: {0}", DateTimeOffset.Now); await base.StartAsync(cancellationToken); } //重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { //模擬服務中的處理邏輯,這里我們僅輸出一條日志,並且等待1秒鍾時間 Console.WriteLine("Worker running at: {0}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } //重寫BackgroundService.StopAsync方法,在結束服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Worker stopping at: {0}", DateTimeOffset.Now); await base.StopAsync(cancellationToken); } } }
其效果和ILogger接口的日志輸出方法類似:
不過由於ILogger接口的日志輸出方法,也可以輸出信息到控制台上,所以我還是更推薦使用ILogger接口來輸出調試信息,畢竟它更適合做日志記錄。
不要讓線程阻塞worker類中重寫的StartAsync、ExecuteAsync、StopAsync方法
注意不要讓你的代碼阻塞worker類中重寫的StartAsync、ExecuteAsync、StopAsync方法。
因為StartAsync方法負責啟動Worker Service,如果調用StartAsync方法的線程被一直阻塞了,那么Worker Service的啟動就一直完成不了。
同理StopAsync方法負責結束Worker Service,如果調用StopAsync方法的線程被一直阻塞了,那么Worker Service的結束就一直完成不了。
這里主要說明下為什么ExecuteAsync方法不能被阻塞,我們嘗試把本例中的ExecuteAsync方法改為如下代碼:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); Thread.Sleep(1000);//使用Thread.Sleep進行同步等待,調用ExecuteAsync方法的線程會一直執行這里的循環,被不停地被阻塞 } await Task.CompletedTask; }
我們將ExecuteAsync方法中的異步等待方法Task.Delay,改為了同步等待方法Thread.Sleep(關於Thread.Sleep和Task.Delay有什么不同,請查看這里)。由於Thread.Sleep方法是將執行線程通過阻塞的方式來進行等待,所以現在調用ExecuteAsync方法的線程會一直執行ExecuteAsync方法中的循環,被不停地被阻塞,除非ExecuteAsync方法中的循環結束,那么調用ExecuteAsync方法的線程會被一直卡在ExecuteAsync方法中。現在我們在Visual Studio中運行Worker Service,執行結果如下:
我們可以看到當我們在控制台中使用快捷鍵"Ctrl+C"試圖停止Worker Service后(上圖紅色框中輸出的日志),ExecuteAsync方法中的循環還是在不停地運行來輸出日志,這說明ExecuteAsync方法的CancellationToken參數的IsCancellationRequested屬性還是返回的false,所以這就是問題所在,如果我們直接用調用ExecuteAsync方法的線程去做循環,來執行windows服務或linux守護程序的處理邏輯,會導致Worker Service無法被正常停止,因為ExecuteAsync方法的CancellationToken參數沒有被更新。
所以,那些很耗時並且要循環處理的 windows服務或linux守護程序 的處理邏輯,應該要放到另外的線程中去執行,而不是由調用ExecuteAsync方法的線程去執行。
所以假設我們現在有三個 windows服務或linux守護程序 的邏輯現在要被處理,我們可以將它們放到三個新的線程中去執行,如下代碼所示:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWorkerService { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } //重寫BackgroundService.StartAsync方法,在開始服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now); await base.StartAsync(cancellationToken); } //第一個 windows服務或linux守護程序 的處理邏輯,由RunTaskOne方法內部啟動的Task任務線程進行處理,同樣可以從參數CancellationToken stoppingToken中的IsCancellationRequested屬性,得知Worker Service服務是否已經被停止 protected Task RunTaskOne(CancellationToken stoppingToken) { return Task.Run(() => { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("RunTaskOne running at: {time}", DateTimeOffset.Now); Thread.Sleep(1000); } }, stoppingToken); } //第二個 windows服務或linux守護程序 的處理邏輯,由RunTaskTwo方法內部啟動的Task任務線程進行處理,同樣可以從參數CancellationToken stoppingToken中的IsCancellationRequested屬性,得知Worker Service服務是否已經被停止 protected Task RunTaskTwo(CancellationToken stoppingToken) { return Task.Run(() => { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("RunTaskTwo running at: {time}", DateTimeOffset.Now); Thread.Sleep(1000); } }, stoppingToken); } //第三個 windows服務或linux守護程序 的處理邏輯,由RunTaskThree方法內部啟動的Task任務線程進行處理,同樣可以從參數CancellationToken stoppingToken中的IsCancellationRequested屬性,得知Worker Service服務是否已經被停止 protected Task RunTaskThree(CancellationToken stoppingToken) { return Task.Run(() => { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("RunTaskThree running at: {time}", DateTimeOffset.Now); Thread.Sleep(1000); } }, stoppingToken); } //重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { Task taskOne = RunTaskOne(stoppingToken); Task taskTwo = RunTaskTwo(stoppingToken); Task taskThree = RunTaskThree(stoppingToken); await Task.WhenAll(taskOne, taskTwo, taskThree);//使用await關鍵字,異步等待RunTaskOne、RunTaskTwo、RunTaskThree方法返回的三個Task對象完成,這樣調用ExecuteAsync方法的線程會立即返回,不會卡在這里被阻塞 } catch (Exception ex) { //RunTaskOne、RunTaskTwo、RunTaskThree方法中,異常捕獲后的處理邏輯,這里我們僅輸出一條日志 _logger.LogError(ex.Message); } finally { //Worker Service服務停止后,如果有需要收尾的邏輯,可以寫在這里 } } //重寫BackgroundService.StopAsync方法,在結束服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now); await base.StopAsync(cancellationToken); } } }
所以現在調用ExecuteAsync方法的線程就不會被阻塞了,在Visual Studio中運行Worker Service,執行結果如下:
可以看到這次,當我們在控制台中使用快捷鍵"Ctrl+C"試圖停止Worker Service后(上圖紅色框中輸出的日志),ExecuteAsync方法就立即停止運行了,所以這里再次強調千萬不要去阻塞調用ExecuteAsync方法的線程!
另外上面代碼中,我們在worker類重寫的ExecuteAsync方法中放了一個finally代碼塊,這個代碼塊可以用來執行一些Worker Service服務停止后(例如停止 windows服務或linux守護程序 時)的一些收尾代碼邏輯(例如關閉數據庫連接、釋放資源等),我更傾向於使用ExecuteAsync方法中的finally代碼塊來做Worker Service的收尾工作,而不是在worker類重寫的StopAsync方法中來做收尾工作(從BackgroundService的源代碼,我們可以看出worker類的StopAsync方法是有可能比ExecuteAsync方法先完成的,所以Worker Service的收尾工作應該放到ExecuteAsync方法中的finally代碼塊),因為ExecuteAsync方法中的finally代碼塊,肯定是在RunTaskOne、RunTaskTwo、RunTaskThree方法返回的三個Task對象執行完畢后才執行的。
StopAsync方法
這里再說下BackgroundService類的StopAsync方法,這個方法官方的解釋是:當Worker Service進行graceful shutdown的時候會被執行,這個graceful shutdown有一些限制條件,我在windows操作系統下進行了測試,當在windows服務管理列表中停止或重啟服務時,BackgroundService類的StopAsync方法是會被執行的,同時上面代碼ExecuteAsync方法中的finally代碼塊也會被執行,但是當我在windows操作系統中,直接點關機或重啟時,BackgroundService類的StopAsync方法沒有被執行到,同時上面代碼ExecuteAsync方法中的finally代碼塊也沒有被執行。。。這說明BackgroundService類的StopAsync方法,並不是在Worker Service服務停止的時候肯定會被執行,例如關機或重啟就不會被執行,所以如果我們把很重要的Worker Service服務結束邏輯放到StopAsync方法中,是有安全隱患的。
目前從GitHub上得知這是.NET Core 3.0的一個Bug,微軟會在.NET 5.0中修復這個問題,詳情查看這里。
在前面的例子中,可以看到我們在一個Worker類中定義了三個方法RunTaskOne、RunTaskTwo、RunTaskThree,來執行三個 windows服務或linux守護程序 的邏輯。
其實我們還可以在一個Worker Service項目中,定義和執行多個Worker類,而不是把所有的代碼邏輯都放在一個Worker類中。
首先我們定義第一個Worker類WorkerOne:
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DemoWorkerService { public class WorkerOne : BackgroundService { private readonly ILogger<Worker> _logger; public WorkerOne(ILogger<Worker> logger) { _logger = logger; } //重寫BackgroundService.StartAsync方法,在開始服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("WorkerOne starting at: {time}", DateTimeOffset.Now); await base.StartAsync(cancellationToken); } //重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { //模擬服務中的處理邏輯,這里我們僅輸出一條日志,並且等待1秒鍾時間 _logger.LogInformation("WorkerOne running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } //重寫BackgroundService.StopAsync方法,在結束服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("WorkerOne stopping at: {time}", DateTimeOffset.Now); await base.StopAsync(cancellationToken); } } }
接着我們定義第二個Worker類WorkerTwo:
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DemoWorkerService { public class WorkerTwo : BackgroundService { private readonly ILogger<Worker> _logger; public WorkerTwo(ILogger<Worker> logger) { _logger = logger; } //重寫BackgroundService.StartAsync方法,在開始服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("WorkerTwo starting at: {time}", DateTimeOffset.Now); await base.StartAsync(cancellationToken); } //重寫BackgroundService.ExecuteAsync方法,封裝windows服務或linux守護程序中的處理邏輯 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //如果服務被停止,那么下面的IsCancellationRequested會返回true,我們就應該結束循環 while (!stoppingToken.IsCancellationRequested) { //模擬服務中的處理邏輯,這里我們僅輸出一條日志,並且等待1秒鍾時間 _logger.LogInformation("WorkerTwo running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } //重寫BackgroundService.StopAsync方法,在結束服務的時候,執行一些處理邏輯,這里我們僅輸出一條日志 public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("WorkerTwo stopping at: {time}", DateTimeOffset.Now); await base.StopAsync(cancellationToken); } } }
然后我們在Program類中,將WorkerOne和WorkerTwo服務添加到DI container中:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace DemoWorkerService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddHostedService<WorkerOne>(); services.AddHostedService<WorkerTwo>(); }); } }
然后在Visual Studio中運行Worker Service,執行結果如下:
在控制台中使用快捷鍵"Ctrl+C"來停止Worker Service的運行后,我用三個紅色框,將WorkerOne和WorkerTwo類中重寫StartAsync、ExecuteAsync、StopAsync方法的輸出結果標識了出來,可以看到WorkerOne和WorkerTwo類都被執行了,並且都輸出了日志信息。
1.在項目中添加nuget包:Microsoft.Extensions.Hosting.WindowsServices
2.然后在program.cs內部,將UseWindowsService()添加到CreateHostBuilder
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseWindowsService() .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); });
注意,在非 Windows 平台上調用 UseWindowsService 方法也是不會報錯的,非 Windows 平台會忽略此調用。
3.執行一下命令發布項目
dotnet publish -c Release -o C:\WorkerPub
在CMD中執行:
當然,也可以在Visual Studio中用項目自身的發布向導來將Worker Service項目發布到文件夾"C:\WorkerPub"中:
默認情況下Worker Service項目會被發布為一個exe文件:
4.然后使用sc.exe工具來管理服務,輸入一下命令創建為windows服務,這里我們將DemoWorkerService.exe創建為了名為NETCoreDemoWorkerService的windows服務:
sc.exe create NETCoreDemoWorkerService binPath=C:\WorkerPub\DemoWorkerService.exe
在CMD中執行,注意這里要用管理員模式(Run as administrator)啟動CMD:
查看服務狀態使用一下命令:
sc.exe query NETCoreDemoWorkerService
在CMD中執行(Run as administrator):
啟動命令:
sc.exe start NETCoreDemoWorkerService
在CMD中執行(Run as administrator):
在windows服務列表查看,NETCoreDemoWorkerService已安裝成功:
停用 、刪除命令:
sc.exe stop NETCoreDemoWorkerService sc.exe delete NETCoreDemoWorkerService
在CMD中執行(Run as administrator):
部署linux守護程序也是很方便的執行一下兩個步驟即可:
- 添加Microsoft.Extensions.Hosting.Systemd NuGet包到項目中,並告訴你的新Worker,其生命周期由systemd管理!
- 將UseSystemd()添加到主機構建器中
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSystemd() .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); });
同樣,在 Windows 平台上調用 UseSystemd 方法也是不會報錯的,Windows 平台會忽略此調用。
Worker Service in ASP .NET Core