• 如何在跨平台的环境中创建可以跨平台的后台服务,它就是 Worker Service。


    一、简介

        最近,有一个项目要使用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 程序入口的类型。

    复制代码
    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服务添加到 IOC 容器中。

        1.4、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.cs 只是一个简单的类,它继承自BackgroundService ,而后者又实现IHostedService接口。

        2、依赖注入(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;
            }
        }
    }
    复制代码

          2.1、我们可以在Program类中的ConfigureServices方法中,使用services参数(该参数是IServiceCollection接口的对象,它就是IOC容器)来配置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) =>
                    {
                //配置IContainer接口和MyContainer类的依赖注入关系
                services.AddSingleton<IContainer,MyContainer>(); services.AddHostedService<Worker>(); }); } }
    复制代码

           2.2、然后在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);
                }
            }
        }
    }
    复制代码

    ·       2.3、注意上面 Worker 类的构造函数中,使用了.NET Core自带的日志组件接口对象ILogger,它也是通过构造函数注入到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);
                }
            }
        }
    }
    复制代码

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

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

    复制代码
    //重写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() 方法和 StopAsyn() 方法会执行一些 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接口来输出调试信息,毕竟它更适合做日志记录。

          4、不要让线程阻塞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对象执行完毕后才执行的。

           5、在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<WorkerTwo> _logger;
    
            public WorkerTwo(ILogger<WorkerTwo> 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类都被执行了,并且都输出了日志信息。

         6、部署为Windows服务运行

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

              

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

    复制代码
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService();
            {
                services.AddHostedService<Worker>();
            });
    复制代码

                注意,在非 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服务:

    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):

                  

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

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

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

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

    复制代码
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSystemd();
            {
                services.AddHostedService<Worker>();
            });
    复制代码

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

    三、总结

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

  • 相关阅读:
    Java注释Override、Deprecated、SuppressWarnings详解
    android: 实现跨程序数据共享
    android: 创建自己的内容提供器
    android: 通过内容提供器读取系统联系人
    android: 内容提供器简介
    android: UriMatcher的用法
    java中static{}语句块详解
    android: SQLite 数据库的最佳实践
    android: SQLite使用 SQL 操作数据库
    android: SQLite查询数据
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/15834732.html
Copyright © 2020-2023  润新知