利用.NET Core中的Worker Service,來創建windows服務或linux守護程序


導航

 

.NET Core 3.0新增了Worker Service的新項目模板,可以編寫長時間運行的后台服務,並且能輕松的部署成windows服務或linux守護程序。如果安裝的vs2019是中文版本,Worker Service的項目名稱就變成了輔助角色服務。Worker Service 咱也不知道怎么翻譯成了這個名稱,咱也不敢亂翻譯,下文就保持原名稱。。。,本文將會演示如何創建一個Worker Service項目,並且部署為windows服務或linux守護程序運行;

 

開始創建worker service 項目

創建新項目——》選擇 Worker Service(輔助角色服務)

取一個項目名稱:DemoWorkerService

項目創建成功之后,您會看到創建了兩個類:Program和Worker。

 

Program.cs

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中。

 

Worker.cs

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接口。

 

依賴注入(DI)

我們可以在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.StartAsyncbase.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類型,所以如同上面代碼所示,我們可以使用asyncawait關鍵字將它們重寫為異步函數,來提高程序的性能。現在我們在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 Service中運行多個Worker類

在前面的例子中,可以看到我們在一個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類都被執行了,並且都輸出了日志信息。

 

部署為Windows服務運行

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守護程序運行

部署linux守護程序也是很方便的執行一下兩個步驟即可:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSystemd()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
        });

同樣,在 Windows 平台上調用 UseSystemd 方法也是不會報錯的,Windows 平台會忽略此調用。

 

 

參考文獻:

.NET Core創建Worker Services

Worker Service in ASP .NET Core

 


免責聲明!

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



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