一、简介

    最近,有一个项目要使用 Windows 服务,来做为一个软件项目的载体。我想了想,都已经到了跨平台的时代了,会不会有替换 Windows 服务的技术出现呢?于是,在网络上疯狂的搜索了一番,真实皇天不负苦心人,找到了一个答案,那就是 Worker Service。听说在 NET Core 3.0 的时代就新增了 Worker Service 的新项目模板,可以编写长时间运行的后台服务,并且能轻松的部署成 windows 服务或 linux 守护程序。如果安装的 vs2019 是中文版本,Worker Service 的项目名称就变成了辅助角色服务。我也没有学习的太深入,就是把自己的使用过程展示出来,本文将会演示如何创建一个 Worker Service 项目,并且部署为 windows 服务或 linux 守护程序运行;废话不多说,开始。

 二、开始实战

   1、开始创建 worker service 项目

        1.1、创建新项目——》选择 Worker Service(辅助角色服务)                           

        1.2、给项目取一个名称:DemoWorkerService

                        

        项目创建成功之后,您会看到创建了两个类:Program.cs  和 Worker.cs。

        

    1.3、Program.cs 程序入口的类型。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.Extensions.DependencyInjection;
 6 using Microsoft.Extensions.Hosting;
 7 
 8 namespace DemoWorkerService
 9 {
10     public class Program
11     {
12         public static void Main(string[] args)
13         {
14             CreateHostBuilder(args).Build().Run();
15         }
16 
17         public static IHostBuilder CreateHostBuilder(string[] args) =>
18             Host.CreateDefaultBuilder(args)
19                 .ConfigureServices((hostContext, services) =>
20                 {
21                     services.AddHostedService<Worker>();
22                 });
23     }
24 }

 

      Program 类跟 ASP.NET Core Web 应用程序非常类似,不同之处没有了 startup 类,并且把 worker 服务添加到 IOC 容器中。

    1.4、Worker.cs  具体的承担任务的工作类型。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using Microsoft.Extensions.Hosting;
 7 using Microsoft.Extensions.Logging;
 8 
 9 namespace DemoWorkerService
10 {
11     public class Worker : BackgroundService
12     {
13         private readonly ILogger<Worker> _logger;
14 
15         public Worker(ILogger<Worker> logger)
16         {
17             _logger = logger;
18         }
19 
20         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
21         {
22             while (!stoppingToken.IsCancellationRequested)
23             {
24                 _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
25                 await Task.Delay(1000, stoppingToken);
26             }
27         }
28     }
29 }

 

      Worker.cs 只是一个简单的类,它继承自 BackgroundService ,而后者又实现 IHostedService 接口。

    2、依赖注入(DI)

      我们可以在 Program 类中的 ConfigureServices 方法中,配置 Worker 类要用到的类型,这叫做“构造函数注入”,假如我们现在有 IContainer 接口和 MyContainer 类,它们是一组类型:

 1 public interface IContainer
 2 {
 3     string ContainerName
 4     {
 5         get;
 6         set;
 7     }
 8 }
 9 
10 public class MyContainer : IContainer
11 {
12     protected string containerName = "Custom my container";
13 
14     public string ContainerName
15     {
16         get
17         {
18             return containerName;
19         }
20         set
21         {
22             containerName = value;
23         }
24     }
25 }

 

      2.1、我们可以在 Program 类中的 ConfigureServices 方法中,使用 services 参数(该参数是 IServiceCollection 接口的对象,它就是 IOC 容器)来配置 IContainer 接口和 MyContainer 类的对应关系:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using DemoWorkerService.Model;
 6 using Microsoft.Extensions.DependencyInjection;
 7 using Microsoft.Extensions.Hosting;
 8 
 9 namespace DemoWorkerService
10 {
11     public class Program
12     {
13         public static void Main(string[] args)
14         {
15             CreateHostBuilder(args).Build().Run();
16         }
17 
18         public static IHostBuilder CreateHostBuilder(string[] args) =>
19             Host.CreateDefaultBuilder(args)
20                 .ConfigureServices((hostContext, services) =>
21                 {
22             //配置IContainer接口和MyContainer类的依赖注入关系
23             services.AddSingleton<IContainer,MyContainer>();
24                      services.AddHostedService<Worker>();
25                 });
26     }
27 }

 

       2.2、然后在 Worker 类的构造函数中,通过构造函数注入,来使用其类型。Worker Service 就会使用DI(依赖注入)自动注入 IContainer 类型的参数:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using DemoWorkerService.Model;
 7 using Microsoft.Extensions.Hosting;
 8 using Microsoft.Extensions.Logging;
 9 
10 namespace DemoWorkerService
11 {
12     public class Worker : BackgroundService
13     {
14         private readonly ILogger<Worker> _logger;
15         private readonly IContainer _container;
16 
17         //Worker Service会自动依赖注入Worker构造函数的IContainer container参数
18         public Worker(ILogger<Worker> logger, IContainer container)
19         {
20             _logger = logger;
21             _container = container;
22         }
23 
24         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
25         {
26             while (!stoppingToken.IsCancellationRequested)
27             {
28                 _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
29                 await Task.Delay(1000, stoppingToken);
30             }
31         }
32     }
33 }

 

·       2.3、注意上面 Worker 类的构造函数中,使用了 .NET Core 自带的日志组件接口对象 ILogger,它也是通过构造函数注入到 Worker 类型内部来使用的,和 ASP.NET Core 中 Controller 的构造函数注入类似(关于 ILogger,可以查看这里了解),我们也可以不用日志组件,给 Worker 类定义无参的默认构造函数。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using Microsoft.Extensions.Hosting;
 7 
 8 namespace DemoWorkerService
 9 {
10     public class Worker : BackgroundService
11     {
12         public Worker()
13         {
14         }
15 
16         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
17         {
18             while (!stoppingToken.IsCancellationRequested)
19             {
20                 await Task.Delay(1000, stoppingToken);
21             }
22         }
23     }
24 }

 

    3、重写 BackgroundService 类的 StartAsync()、ExecuteAsync()、StopAsync() 方法

        我们可以通过 override ExecuteAsync 方法来完成自己要做的事情,该方法实际上属于 BackgroundService 类,我们只是在 Worker 类中重写(override)了它而已。在这个方法中就是我们要处理的逻辑,原则上来说这个逻辑应该是在一个死循环中,并且通过 ExecuteAsync 方法传入的 CancellationToken 参数对象,来判断是否应该结束循环。例如:如果 Windows 服务被停止,那么参数中 CancellationToken 类的 IsCancellationRequested 属性会返回 true,那么我们应该停止 ExecuteAsync 方法中的循环,来结束整个服务过程的执行:

 1 //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
 2 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 3 {
 4     //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
 5     while (!stoppingToken.IsCancellationRequested)
 6     {
 7         //模拟服务中的处理逻辑,这里我们仅输出一条日志,并且等待1秒钟时间
 8         _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
 9         await Task.Delay(1000, stoppingToken);
10     }
11 }

        此外,我们也可以在 Worker 类中重写 BackgroundService.StartAsync() 方法和 BackgroundService.StopAsync() 方法(注意重写时,不要忘记在 Worker 类中调用 base.StartAsync() 和 base.StopAsync() 方法,因为 BackgroundService 类的 StartAsync() 方法和 StopAsyn() 方法会执行一些 Worker Service 的核心代码)。在开始和结束 Worker Service 服务(例如,开始和停止 windows 服务)的时候,来执行一些处理逻辑,本例中我们分别输出了一条日志:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using Microsoft.Extensions.Hosting;
 7 using Microsoft.Extensions.Logging;
 8 
 9 namespace DemoWorkerService
10 {
11     public class Worker : BackgroundService
12     {
13         private readonly ILogger<Worker> _logger;
14 
15         public Worker(ILogger<Worker> logger)
16         {
17             _logger = logger;
18         }
19 
20         //重写BackgroundService.StartAsync方法,在开始服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
21         public override async Task StartAsync(CancellationToken cancellationToken)
22         {
23             _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now);
24 
25             await base.StartAsync(cancellationToken);
26         }
27 
28         //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
29         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
30         {
31             //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
32             while (!stoppingToken.IsCancellationRequested)
33             {
34                 //模拟服务中的处理逻辑,这里我们仅输出一条日志,并且等待1秒钟时间
35                 _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
36                 await Task.Delay(1000, stoppingToken);
37             }
38         }
39 
40         //重写BackgroundService.StopAsync方法,在结束服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
41         public override async Task StopAsync(CancellationToken cancellationToken)
42         {
43             _logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now);
44 
45             await base.StopAsync(cancellationToken);
46         }
47     }
48 }

        由于 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 接口的日志输出方法:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using Microsoft.Extensions.Hosting;
 7 using Microsoft.Extensions.Logging;
 8 
 9 namespace DemoWorkerService
10 {
11     public class Worker : BackgroundService
12     {
13         public Worker()
14         {
15         }
16 
17         //重写BackgroundService.StartAsync方法,在开始服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
18         public override async Task StartAsync(CancellationToken cancellationToken)
19         {
20             Console.WriteLine("Worker starting at: {0}", DateTimeOffset.Now);
21 
22             await base.StartAsync(cancellationToken);
23         }
24 
25         //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
26         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
27         {
28             //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
29             while (!stoppingToken.IsCancellationRequested)
30             {
31                 //模拟服务中的处理逻辑,这里我们仅输出一条日志,并且等待1秒钟时间
32                 Console.WriteLine("Worker running at: {0}", DateTimeOffset.Now);
33                 await Task.Delay(1000, stoppingToken);
34             }
35         }
36 
37         //重写BackgroundService.StopAsync方法,在结束服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
38         public override async Task StopAsync(CancellationToken cancellationToken)
39         {
40             Console.WriteLine("Worker stopping at: {0}", DateTimeOffset.Now);
41 
42             await base.StopAsync(cancellationToken);
43         }
44     }
45 }

        其效果和 ILogger 接口的日志输出方法类似:

        

        不过由于 ILogger 接口的日志输出方法,也可以输出信息到控制台上,所以我还是更推荐使用 ILogger 接口来输出调试信息,毕竟它更适合做日志记录。

      4、不要让线程阻塞 worker 类中重写的 StartAsync()、ExecuteAsync()、StopAsync() 方法

        注意:不要让你的代码阻塞 Worker 类中重写的 StartAsync()、ExecuteAsync()、StopAsync()方法。

        因为 StartAsync() 方法负责启动 Worker Service,如果调用 StartAsync() 方法的线程被一直阻塞了,那么 Worker Service 的启动就一直完成不了。

        同理 StopAsync() 方法负责结束 Worker Service,如果调用 StopAsync() 方法的线程被一直阻塞了,那么 Worker Service 的结束就一直完成不了。

        这里主要说明下为什么 ExecuteAsync() 方法不能被阻塞,我们尝试把本例中的 ExecuteAsync() 方法改为如下代码:

 1 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 2 {
 3     while (!stoppingToken.IsCancellationRequested)
 4     {
 5         _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
 6         Thread.Sleep(1000);//使用Thread.Sleep进行同步等待,调用ExecuteAsync方法的线程会一直执行这里的循环,被不停地被阻塞
 7     }
 8 
 9     await Task.CompletedTask;
10 }

        我们将 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 守护程序 的逻辑现在要被处理,我们可以将它们放到三个新的线程中去执行,如下代码所示:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Threading;
  5 using System.Threading.Tasks;
  6 using Microsoft.Extensions.Hosting;
  7 using Microsoft.Extensions.Logging;
  8 
  9 namespace DemoWorkerService
 10 {
 11     public class Worker : BackgroundService
 12     {
 13         private readonly ILogger<Worker> _logger;
 14 
 15         public Worker(ILogger<Worker> logger)
 16         {
 17             _logger = logger;
 18         }
 19 
 20         //重写BackgroundService.StartAsync方法,在开始服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
 21         public override async Task StartAsync(CancellationToken cancellationToken)
 22         {
 23             _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now);
 24 
 25             await base.StartAsync(cancellationToken);
 26         }
 27 
 28         //第一个 windows服务或linux守护程序 的处理逻辑,由RunTaskOne方法内部启动的Task任务线程进行处理,同样可以从参数CancellationToken stoppingToken中的IsCancellationRequested属性,得知Worker Service服务是否已经被停止
 29         protected Task RunTaskOne(CancellationToken stoppingToken)
 30         {
 31             return Task.Run(() =>
 32             {
 33                 //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
 34                 while (!stoppingToken.IsCancellationRequested)
 35                 {
 36                     _logger.LogInformation("RunTaskOne running at: {time}", DateTimeOffset.Now);
 37                     Thread.Sleep(1000);
 38                 }
 39             }, stoppingToken);
 40         }
 41 
 42         //第二个 windows服务或linux守护程序 的处理逻辑,由RunTaskTwo方法内部启动的Task任务线程进行处理,同样可以从参数CancellationToken stoppingToken中的IsCancellationRequested属性,得知Worker Service服务是否已经被停止
 43         protected Task RunTaskTwo(CancellationToken stoppingToken)
 44         {
 45             return Task.Run(() =>
 46             {
 47                 //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
 48                 while (!stoppingToken.IsCancellationRequested)
 49                 {
 50                     _logger.LogInformation("RunTaskTwo running at: {time}", DateTimeOffset.Now);
 51                     Thread.Sleep(1000);
 52                 }
 53             }, stoppingToken);
 54         }
 55 
 56         //第三个 windows服务或linux守护程序 的处理逻辑,由RunTaskThree方法内部启动的Task任务线程进行处理,同样可以从参数CancellationToken stoppingToken中的IsCancellationRequested属性,得知Worker Service服务是否已经被停止
 57         protected Task RunTaskThree(CancellationToken stoppingToken)
 58         {
 59             return Task.Run(() =>
 60             {
 61                 //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
 62                 while (!stoppingToken.IsCancellationRequested)
 63                 {
 64                     _logger.LogInformation("RunTaskThree running at: {time}", DateTimeOffset.Now);
 65                     Thread.Sleep(1000);
 66                 }
 67             }, stoppingToken);
 68         }
 69 
 70         //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
 71         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 72         {
 73             try
 74             {
 75                 Task taskOne = RunTaskOne(stoppingToken);
 76                 Task taskTwo = RunTaskTwo(stoppingToken);
 77                 Task taskThree = RunTaskThree(stoppingToken);
 78 
 79                 await Task.WhenAll(taskOne, taskTwo, taskThree);//使用await关键字,异步等待RunTaskOne、RunTaskTwo、RunTaskThree方法返回的三个Task对象完成,这样调用ExecuteAsync方法的线程会立即返回,不会卡在这里被阻塞
 80             }
 81             catch (Exception ex)
 82             {
 83                 //RunTaskOne、RunTaskTwo、RunTaskThree方法中,异常捕获后的处理逻辑,这里我们仅输出一条日志
 84                 _logger.LogError(ex.Message);
 85             }
 86             finally
 87             {
 88                 //Worker Service服务停止后,如果有需要收尾的逻辑,可以写在这里
 89             }
 90         }
 91 
 92         //重写BackgroundService.StopAsync方法,在结束服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
 93         public override async Task StopAsync(CancellationToken cancellationToken)
 94         {
 95             _logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now);
 96 
 97             await base.StopAsync(cancellationToken);
 98         }
 99     }
100 }

        所以现在调用 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 对象执行完毕后才执行的。

       5、在 Worker Service 中运行多个 Worker 类

          在前面的例子中,可以看到我们在一个 Worker 类中定义了三个方法 RunTaskOne、RunTaskTwo、RunTaskThree,来执行三个 windows 服务或 linux守护程序 的逻辑。

          其实我们还可以在一个 Worker Service 项目中,定义和执行多个 Worker 类,而不是把所有的代码逻辑都放在一个 Worker 类中。

          首先我们定义第一个 Worker 类 WorkerOne:

 1 using Microsoft.Extensions.Hosting;
 2 using Microsoft.Extensions.Logging;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace DemoWorkerService
10 {
11     public class WorkerOne : BackgroundService
12     {
13         private readonly ILogger<Worker> _logger;
14 
15         public WorkerOne(ILogger<Worker> logger)
16         {
17             _logger = logger;
18         }
19 
20         //重写BackgroundService.StartAsync方法,在开始服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
21         public override async Task StartAsync(CancellationToken cancellationToken)
22         {
23             _logger.LogInformation("WorkerOne starting at: {time}", DateTimeOffset.Now);
24 
25             await base.StartAsync(cancellationToken);
26         }
27 
28         //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
29         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
30         {
31             //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
32             while (!stoppingToken.IsCancellationRequested)
33             {
34                 //模拟服务中的处理逻辑,这里我们仅输出一条日志,并且等待1秒钟时间
35                 _logger.LogInformation("WorkerOne running at: {time}", DateTimeOffset.Now);
36                 await Task.Delay(1000, stoppingToken);
37             }
38         }
39 
40         //重写BackgroundService.StopAsync方法,在结束服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
41         public override async Task StopAsync(CancellationToken cancellationToken)
42         {
43             _logger.LogInformation("WorkerOne stopping at: {time}", DateTimeOffset.Now);
44 
45             await base.StopAsync(cancellationToken);
46         }
47     }
48 }

        接着我们定义第二个 Worker 类 WorkerTwo:

 1 using Microsoft.Extensions.Hosting;
 2 using Microsoft.Extensions.Logging;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace DemoWorkerService
10 {
11     public class WorkerTwo : BackgroundService
12     {
13         private readonly ILogger<WorkerTwo> _logger;
14 
15         public WorkerTwo(ILogger<WorkerTwo> logger)
16         {
17             _logger = logger;
18         }
19 
20         //重写BackgroundService.StartAsync方法,在开始服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
21         public override async Task StartAsync(CancellationToken cancellationToken)
22         {
23             _logger.LogInformation("WorkerTwo starting at: {time}", DateTimeOffset.Now);
24 
25             await base.StartAsync(cancellationToken);
26         }
27 
28         //重写BackgroundService.ExecuteAsync方法,封装windows服务或linux守护程序中的处理逻辑
29         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
30         {
31             //如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
32             while (!stoppingToken.IsCancellationRequested)
33             {
34                 //模拟服务中的处理逻辑,这里我们仅输出一条日志,并且等待1秒钟时间
35                 _logger.LogInformation("WorkerTwo running at: {time}", DateTimeOffset.Now);
36                 await Task.Delay(1000, stoppingToken);
37             }
38         }
39 
40         //重写BackgroundService.StopAsync方法,在结束服务的时候,执行一些处理逻辑,这里我们仅输出一条日志
41         public override async Task StopAsync(CancellationToken cancellationToken)
42         {
43             _logger.LogInformation("WorkerTwo stopping at: {time}", DateTimeOffset.Now);
44 
45             await base.StopAsync(cancellationToken);
46         }
47     }
48 }

        然后我们在 Program 类中,将 WorkerOne 和 WorkerTwo 服务添加到 DI container 中:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading;
 5 using System.Threading.Tasks;
 6 using Microsoft.Extensions.DependencyInjection;
 7 using Microsoft.Extensions.Hosting;
 8 
 9 namespace DemoWorkerService
10 {
11     public class Program
12     {
13         public static void Main(string[] args)
14         {
15             CreateHostBuilder(args).Build().Run();
16         }
17 
18         public static IHostBuilder CreateHostBuilder(string[] args) =>
19             Host.CreateDefaultBuilder(args)
20                 .ConfigureServices((hostContext, services) =>
21                 {
22                     services.AddHostedService<WorkerOne>();
23                     services.AddHostedService<WorkerTwo>();
24                 });
25     }
26 }
27 
28

        然后在Visual Studio中运行Worker Service,执行结果如下:

                  

        在控制台中使用快捷键 "Ctrl+C" 来停止 Worker Service 的运行后,我用三个红色框,将 WorkerOne 和 WorkerTwo 类中重写 StartAsync、ExecuteAsync、StopAsync 方法的输出结果标识了出来,可以看到 WorkerOne 和 WorkerTwo 类都被执行了,并且都输出了日志信息。

 

     6、部署为Windows服务运行

          6.1、在项目中添加nuget包:Microsoft.Extensions.Hosting.WindowsServices

          

          6.2、然后在program.cs内部,将UseWindowsService()添加到CreateHostBuilder

1 public static IHostBuilder CreateHostBuilder(string[] args) =>
2     Host.CreateDefaultBuilder(args)
3         .UseWindowsService();
4         {
5             services.AddHostedService<Worker>();
6         });

              注意,在非 Windows 平台上调用 UseWindowsService 方法也是不会报错的,非 Windows 平台会忽略此调用。

          6.3、执行一下命令发布项目

dotnet publish  -c Release -o C:\WorkerPub

            在CMD中执行:

            

            当然,也可以在Visual Studio中用项目自身的发布向导来将Worker Service项目发布到文件夹"C:\WorkerPub"中:

            

            

            默认情况下Worker Service项目会被发布为一个exe文件:

            

            6.4、然后使用 sc.exe 工具来管理服务,输入一下命令创建为 windows 服务,这里我们将 DemoWorkerService.exe 创建为了名为NETCoreDemoWorkerService 的 windows 服务(如果服务名称或者文件路径有空格,必须用引号括起来,如:"My Best Server"):

sc.exe create NETCoreDemoWorkerService binPath=C:\WorkerPub\DemoWorkerService.exe

              以上 binPath=C:\WorkerPub\DemoWorkerService.exe 是绝对地址的方式,如果是相对地址可以写成 binPath=./WorkerPub\DemoWorkerService.exe

sc.exe create NETCoreDemoWorkerService binPath=DemoWorkerService.exe

              在CMD中执行,注意这里要用管理员模式(Run as administrator)启动CMD:

              

              查看服务状态使用一下命令:

sc.exe query NETCoreDemoWorkerService

              在CMD中执行(Run as administrator):

              

              启动命令(这个命令是在Net Core3.1中,以后可能有变)

sc.exe start NETCoreDemoWorkerService

              在 CMD 中执行(Run as administrator):

              

              在 windows 服务列表查看,NETCoreDemoWorkerService 已安装成功:

              

              停用 、删除命令:

sc.exe stop NETCoreDemoWorkerService
sc.exe delete NETCoreDemoWorkerService

              在CMD中执行(Run as administrator):

              

 

        7、部署作为Linux守护程序运行

              部署 linux 守护程序也是很方便的执行一下两个步骤即可:

              添加 Microsoft.Extensions.Hosting.Systemd NuGet包到项目中,并告诉你的新Worker,其生命周期由systemd管理!

              将 UseSystemd() 添加到主机构建器中

1 public static IHostBuilder CreateHostBuilder(string[] args) =>
2     Host.CreateDefaultBuilder(args)
3         .UseSystemd();
4         {
5             services.AddHostedService<Worker>();
6         });

              同样,在 Windows 平台上调用 UseSystemd 方法也是不会报错的,Windows 平台会忽略此调用。

 

三、总结

      个人感觉 Worker Service 比 Windows 服务好用多了,无论是安装、配置和卸载都很方便,如果大家使用过 Windows服务,就知道它的过程还是很多的,又要添加安装程序等等。现在有了这个项目,以后在开发后台服务就是容易多了。好了,就是这么多。继续努力吧。

posted on 2022-01-22 18:33  可均可可  阅读(610)  评论(0编辑  收藏  举报