Windows 服务 以及 NET Core Windows 服务 BackgroundService

陶裕
2023-12-01

开发 Windows 服务应用
在 Windows 服务中托管 ASP.NET Core
如何:调试 Windows 服务应用程序 
文档目录  
.NET 文档  
ASP.NET 文档  
在 ASP.NET Core 中使用托管服务实现后台任务  
ASP.NET Core 计时的后台任务
使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务  

1、Windows服务程序相关的命名空间
      涉及到以下两个:System.ServiceProcess 和 System.Diagnostics。

2、Windows 服务开发框架(第三方组件)
      Topshelf:一款非常好用的 Windows 服务开发框架
      Install-Package Topshelf
      Install-Package Topshelf.Log4Net

3、托管服务 后台任务
在 ASP.NET Core 中使用托管服务实现后台任务
使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务  

对于 ASP.NET Core 应用,将隐式添加 Microsoft.Extensions.Hosting 引用。
托管服务实现 IHostedService 接口。 该接口为主机托管的对象定义了两种方法:
● StartAsync(CancellationToken) – StartAsync 包含启动后台任务的逻辑。
● StopAsync(CancellationToken) – 主机正常关闭时触发。 
● BackgroundService:是用于实现长时间运行的 IHostedService 的基类。
● 计时的后台任务:定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork 方法。 在 StopAsync 上禁用计时器,并在 Dispose 上处置服务容器时处置计时器
● 在后台任务中使用有作用域的服务
● 排队的后台任务:后台任务队列基于 QueueBackgroundWorkItem
● 如果后台任务与 HTTP (IWebHost) 无关,则应使用 IHost
● 
*
*
*
4、创建服务项目
      打开Visual Studio -> 文件 -> 新建 -> 项目 -> 已安装 -> Visual C# -> Windows 桌面 -> Windows 服务(.NET Framework)

5、文件说明
      Program.cs:主程序入口

using System.ServiceProcess;

namespace MyWindowsService
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new Service1()
            };
            ServiceBase.Run(ServicesToRun);
        }
    }
}

      Service1.cs:编写逻辑代码,F7进入代码页面(或 点击切换代码视图)
      OnStart(string[] args):执行启动服务事件,一般采用线程方式执行方法,便于隔一段事件执行一回。
      OnStop():执行停止服务事件。

using System;
using System.IO;
using System.ServiceProcess;
using System.Threading.Tasks;

namespace MyWindowsService
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 服务开启
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args)
        {
            Task.Factory.StartNew(Handle);
        }

        /// <summary>
        /// 服务关闭
        /// </summary>
        protected override void OnStop()
        {
        }

        /// <summary>
        /// 服务暂停执行代码
        /// </summary>
        protected override void OnPause()
        {
            base.OnPause();
        }

        /// <summary>
        /// 服务恢复执行代码
        /// </summary>
        protected override void OnContinue()
        {
            base.OnContinue();
        }

        /// <summary>
        /// 系统即将关闭执行代码
        /// </summary>
        protected override void OnShutdown()
        {
            base.OnShutdown();
        }

        //需要定时执行的代码段
        private void Handle()
        {
            while (true)
            {
                try
                {
                    var path = AppDomain.CurrentDomain.BaseDirectory + "service.log";
                    var context = "MyWindowsService: Service Stoped " + DateTime.Now + "\n";
                    WriteLogs(path, context);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void WriteLogs(string path, string context)
        {
            var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write);
            var sw = new StreamWriter(fs);
            sw.BaseStream.Seek(0, SeekOrigin.End);
            sw.WriteLine(context);

            sw.Flush();
            sw.Close();
            fs.Close();
        }
    }
}

6、创建服务安装程序
      选中Service1.cs,右键-->查看设计器 ;在设计器中右键-->添加安装程序

7、安装、卸载服务
      安装服务需要InstallUtil.exe,一般在电脑这个目录找到C:\Windows\Microsoft.NET\Framework64\v4.0.30319 
   【注意:在C:\Windows\Microsoft.NET\Framework目录下有很多类似版本,具体去哪个目录要看项目的运行环境,例 如果是.net framework2.0则需要输入 cd C:\Windows\Microsoft.NET\Framework\v2.0.50727】

   dos窗口 -> cmd -> 确定

   cd C:\Windows\Microsoft.NET\Framework64\v4.0.30319

   安装服务: Installutil C:\Debug\你的服务名称.exe

   卸载服务: Installutil /u C:\Debug\<yourproject>.exe

8、查看服务
      我的电脑 -> 右键管理 -> 服务和应用程序 -> 服务 -> 找到服务名称

9、调试服务
      首先确保服务已经安装成功,并且处于已启动
      Visual Studio -> 调试 -> 附加到进程 -> 找到服务名称 -> 确定

---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
1、管理 NuGet 程序包
      Topshelf
      NLog

2、Topshelf.ServiceConfigurators
      void ConstructUsing(ServiceFactory<T> serviceFactory);
      void WhenContinued(Func<T, HostControl, bool> @continue);  //继续运行服务
      void WhenCustomCommandReceived(Action<T, HostControl, int> customCommandReceived);
      void WhenPaused(Func<T, HostControl, bool> pause);                //暂停服务
      void WhenPowerEvent(Func<T, HostControl, PowerEventArguments, bool> powerEvent);
      void WhenSessionChanged(Action<T, HostControl, SessionChangedArguments> sessionChanged);
      void WhenShutdown(Action<T, HostControl> shutdown);           //关闭服务
      void WhenStarted(Func<T, HostControl, bool> start);                  //启动服务
      void WhenStopped(Func<T, HostControl, bool> stop);                //停止服务

3、创建服务项目
      打开Visual Studio -> 文件 -> 新建 -> 项目 -> 已安装 -> Visual C# -> Windows 桌面 -> 控制台应用(.NET Framework)

4、Program.cs:主程序入口

using Topshelf;

namespace TopshelfApp
{
    class Program
    {
        static int Main(string[] args)
        {
            return (int)HostFactory.Run(x =>
            {
                #region 方式1
                x.Service<SyncService>(s =>
                {
                    s.ConstructUsing(name => new SyncService());
                    s.WhenStarted((tc, hostControl) => tc.OnStart(hostControl));
                    s.WhenStopped((tc, hostControl) => tc.OnStop());
                });
                #endregion

                #region 方式2
                //x.Service<TopshelfService>(s =>
                //{
                //    s.ConstructUsing(b => new TopshelfService());
                //    s.WhenStarted(o => o.Start());
                //    s.WhenStopped(o => o.Stop());
                //});
                #endregion

                x.RunAsLocalSystem();
                x.StartAutomatically();
                x.SetDescription("Topshelf_Test_Sync_Service");
                x.SetDisplayName("Topshelf_Test_Sync_Service");
                x.SetServiceName("Topshelf_Test_Sync_Service");
            });
        }
    }
}

5、SyncService.cs

using NLog;
using System;
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;
using Topshelf;

namespace TopshelfApp
{
    public class SyncService
    {
        Logger log = LogManager.GetCurrentClassLogger();
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        SyncNewEmployeeData syncNewEmp;
        SyncAssetUseData syncAssetUse;

        public SyncService() { }

        public bool OnStart(HostControl hostControl)
        {
            log.Info("Sync Service start...");
            try
            {
                //<add key="SyncNewEmployeeTime" value="23:59" />
                var syncNewEmpTime = ConfigurationManager.AppSettings["SyncNewEmployeeTime"];
                syncNewEmp = new SyncNewEmployeeData(syncNewEmpTime);
                syncNewEmp.Start();

                //<add key="SyncAssetUseTime" value="01:59" />
                var syncAssetUseTime = ConfigurationManager.AppSettings["SyncAssetUseTime"];
                syncAssetUse = new SyncAssetUseData(syncAssetUseTime);
                syncAssetUse.Start();

                // Do something continue...
            }
            catch (Exception ex)
            {
                log.Error("Error while trying to initiate services on start methods, detail: " + ex.ToString());
                return false;
            }
            return true;
        }

        public bool OnStop()
        {
            tokenSource.Cancel();
            try
            {
                Task.WaitAll(syncNewEmp.Stop(), syncAssetUse.Stop());
                log.Info("Sync Service stopped.");
            }
            catch (Exception ex)
            {
                log.Error("Error occurred while stopping sync service, detail: " + ex.ToString());
                throw;
            }
            return true;
        }
    }
}

6、TopshelfService

using System;
using System.Timers;

namespace TopshelfApp
{
    public sealed class TopshelfService
    {
        private readonly Timer _timer;

        public TopshelfService()
        {
            _timer = new Timer(1000) { AutoReset = true };
            _timer.Elapsed += (sender, eventArgs) => Console.WriteLine("当前时间:{0}", DateTime.Now);
        }

        public void Start()
        {
            //服务逻辑
            _timer.Start();
        }

        public void Stop()
        {
            _timer.Stop();
        }
    }
}

7、SyncNewEmployeeData.cs

using NLog;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace TopshelfApp
{
    public class SyncNewEmployeeData
    {
        Logger log = LogManager.GetCurrentClassLogger();
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        private Task _t;
        private bool _running = true;
        private string _runningTime;

        public SyncNewEmployeeData(string runningTime)
        {
            //this running time MUST BE in HH:mm format, e.g. 23:59
            _runningTime = runningTime;
        }
        public void Start()
        {
            _running = true;
            log.Info("Sync new employee data service started.");
            Run();
        }
        public Task Stop()
        {
            _running = false;
            tokenSource.Cancel();
            log.Info("Sync new employee data service stopped.");
            return _t;
        }
        private void Run()
        {
            log.Debug("Initiate thread for Synchronizing new employee data.");
            _t = Task.Factory.StartNew(async () =>
            {
                while (_running)
                {
                    try
                    {
                        var dt = DateTime.ParseExact(_runningTime, "HH:mm", CultureInfo.InvariantCulture);

                        if (DateTime.Now > dt)
                        {
                            var gap = dt.AddDays(1) - DateTime.Now;
                            await Task.Delay(gap, tokenSource.Token);
                        }
                        else
                        {
                            var gap = dt - DateTime.Now;
                            await Task.Delay(gap, tokenSource.Token);
                        }

                        // Do something ...
                        //new EmployeeService().GetHrxUserInfo(token);
                    }
                    catch (Exception ex)
                    {
                        log.Error("FAILED to sync ... data, detail: " + ex.Message);
                    }

                    log.Debug($"Sync ... data service status is {(_running ? "running" : "stopping")}");
                }
            });
        }
    }
}

8、SyncAssetUseData.cs

using NLog;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace TopshelfApp
{
    public class SyncAssetUseData
    {
        Logger log = LogManager.GetCurrentClassLogger();
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        private Task _t;
        private bool _running = true;
        private string _runningTime;

        public SyncAssetUseData(string runningTime)
        {
            //this running time MUST BE in HH:mm format, e.g. 23:59
            _runningTime = runningTime;
        }
        public void Start()
        {
            _running = true;
            log.Info("Sync assetUse data service started.");
            Run();
        }
        public Task Stop()
        {
            _running = false;
            tokenSource.Cancel();
            log.Info("Sync assetUse data service stopped.");
            return _t;
        }
        private void Run()
        {
            log.Debug("Initiate thread for Synchronizing assetUse data.");
            _t = Task.Factory.StartNew(async () =>
            {
                while (_running)
                {
                    try
                    {
                        var dt = DateTime.ParseExact(_runningTime, "HH:mm", CultureInfo.InvariantCulture);

                        if (DateTime.Now > dt)
                        {
                            var gap = dt.AddDays(1) - DateTime.Now;
                            await Task.Delay(gap, tokenSource.Token);
                        }
                        else
                        {
                            var gap = dt - DateTime.Now;
                            await Task.Delay(gap, tokenSource.Token);
                        }
                        // Do something ...
                        //new AssetUseRevertService().EmployeeUse();
                    }
                    catch (Exception ex)
                    {
                        log.Error("FAILED to sync ... data, detail: " + ex.Message);
                    }

                    log.Debug($"Sync ... data service status is {(_running ? "running" : "stopping")}");
                }
            });
        }
    }
}

9、安装/卸载服务方法装/卸载服务方法
9、安装/卸载服务方法装/卸载服务方法
9、安装/卸载服务方法装/卸载服务方法
9、安装/卸载服务方法装/卸载服务方法
9、安装/卸载服务方法装/卸载服务方法

     1、cmd -> cd 程序目录(直接定位到exe文件所在目录
           Windows 7   cd C:
           Widows Serve  cd /d C:
     2、安装服务:你的服务名称.exe install
     3、启动服务:你的服务名称.exe start
     4、停止服务:你的服务名称.exe stop
     5、暂停服务:<yourproject>.exe pause
     6、继续服务:<yourproject>.exe continue
     7、删除服务:<yourproject>.exe delete
     8、卸载服务:<yourproject>.exe uninstall (需要执行多次才能卸载服务)

NET Core Windows Service

在 Windows 服务中托管 ASP.NET Core

*、创建一个.net core 控制台项目
*、nuget安装:System.ServiceProcess.ServiceController
*、添加 Windows Service 项【Windows 服务】
*、NotificationService.cs 代码如下

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;

namespace Test.WinService
{
    partial class NotificationService : ServiceBase
    {
        public NotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // TODO: 在此处添加代码以启动服务。
        }

        protected override void OnStop()
        {
            // TODO: 在此处添加代码以执行停止服务所需的关闭操作。
        }
    }
}

*、Program.cs 代码如下

using System;
using System.ServiceProcess;

namespace Test.WinService
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceBase[] services = new ServiceBase[]
            {
                new NotificationService()
            };
            ServiceBase.Run(services);
        }
    }
}

*、BackgroundService Web API

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebAPI
{
    public class TestHostedService : BackgroundService
    {
        private readonly ITestRepository _testRepo;
        private readonly ILogger<HuobiHostedService> _logger;
        private Timer _timer;

        public TestHostedService(ILogger<HuobiHostedService> logger, ITestRepository testRepo)
        {
            _testRepo = testRepo;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

            while (!stoppingToken.IsCancellationRequested)
            {
                int minute = DateTime.Now.Minute;
                if (minute % 15 == 0)
                {
                    _logger.LogInformation($"Ln 50 {minute}");
                }
                await Task.Delay(60000, stoppingToken);
                await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
                await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken).ContinueWith(tsk=> 
                {
                    DoWork();
                });
            }
            if (!stoppingToken.IsCancellationRequested)
            {
                await Task.Run(() =>
                {
                    // 会报错
                    _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
                });
            }
        }

        public override async Task StopAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Huobi Hosted Service is stopping.");
            await base.StopAsync(stoppingToken);
        }

        private void DoWork(object state)
        {
            int minute = DateTime.Now.Minute;
            if (minute % 15 == 0)
            {
                _logger.LogInformation($"Ln 50 {minute}");
            }
        }
    }
}

*
*
*

 类似资料: