编辑
2025-09-29
C#
00

目录

摘要
正文

摘要

Microsoft Windows 服务(过去称为 NT 服务)允许用户创建可在其自身的 Windows 会话中长时间运行的可执行应用程序。

这些服务可在计算机启动时自动启动,可以暂停和重启,并且不显示任何用户界面。 这些功能使服务非常适合在服务器上使用,或者需要长时间运行的功能(不会影响在同一台计算机上工作的其他用户)的情况。

正文

在创建和生成应用程序之后,可以通过运行命令行实用程序 InstallUtil.exe 并将该路径传递给服务的可执行文件来安装它。 然后,可以使用服务控制管理器 来启动、停止、暂停、恢复和配置服务。

服务生存期

服务可以三种基本状态之一存在:RunningPausedStopped。 该服务还可以报告挂起命令的状态:ContinuePendingPausePendingStartPendingStopPending

新建一个项目

image.png

属性

Property设置
CanStopTrue 表示服务将接受请求停止运行;false 将阻止服务被停止。
CanShutdownTrue 表示当服务所在的计算机关机时服务需要接受通知,启用它来调用 OnShutdown 过程。
CanPauseAndContinueTrue 表示服务将接受请求暂停或恢复运行;false 将阻止服务被暂停或恢复。
CanHandlePowerEventTrue 表示服务可处理计算机电源状态更改的通知;false 将阻止向服务通知这些更改。
AutoLogTrue 将在你的服务执行操作时向应用程序事件日志写入信息条目;false 将禁用该功能。 有关详细信息,请参阅如何:记录关于服务的信息。 注意:默认情况下,将 AutoLog 设置为 true

image.png

修改ServiceMain代码编辑器,并填写你想要对 OnStartOnStop 过程的处理。

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace WindowsService1 { public partial class ServiceMain : ServiceBase { public ServiceMain() { InitializeComponent(); } protected override void OnStart(string[] args) { } protected override void OnStop() { } } }

一般这里的过程用时钟,或是死循环完成。

将记录设置为自定义日志

C#
this.AutoLog = false;
  1. 若要使用自定义日志,必须将 AutoLog 设置为 false 。
  2. 在 Windows 服务应用程序中设置 EventLog 组件的一个实例。
  3. 通过调用 CreateEventSource 方法,并指定源字符串和要创建的日志文件的名称,创建一个自定义日志。
  4. Source 组件实例上的 EventLog 属性设置为在步骤 3 中创建的源字符串。
  5. 通过访问 WriteEntry 组件实例上的 EventLog 方法来编写项。
C#
public partial class ServiceMain : ServiceBase { EventLog log = new EventLog(); public ServiceMain() { InitializeComponent(); this.AutoLog = false; if (!System.Diagnostics.EventLog.SourceExists("MySource")) { System.Diagnostics.EventLog.CreateEventSource("MySource", "MyLog"); } log.Source = "MySource"; log.Log = "MyLog"; } protected override void OnStart(string[] args) { log.WriteEntry("In OnStart."); } protected override void OnStop() { log.WriteEntry("In OnStop."); } }

右键添加安装器

image.png

选中ServiceInstaller

image.png

属性

DelayedAutoStart获取或设置一个值,该值指示是否应延迟启动该服务,直到运行其他自动启动的服务。
Description获取或设置服务的说明。
DesignMode获取一个值,用以指示 Component 当前是否处于设计模式。 (继承自 Component)
DisplayName指示向用户标识服务的友好名称。
Events获取附加到此 Component 的事件处理程序的列表。 (继承自 Component)
HelpText获取安装程序集合中所有安装程序的帮助文字。 (继承自 Installer)
Installers获取该安装程序包含的安装程序的集合。 (继承自 Installer)
Parent获取或设置包含该安装程序所属的集合的安装程序。 (继承自 Installer)
ServiceName指示系统用于标识此服务的名称。 此属性必须与要安装的服务的 ServiceName 相同。
ServicesDependedOn指示为使该服务能够运行而必须正在运行的服务。
StartType指示启动此服务的方式和时间。

修改服务运行账号

image.png 安装服务

text
installutil D:\MyProject\ProjectC#\C#\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe

image.png

点击启动

image.png

查看日志文件

image.png

删除服务

text
installutil /u D:\MyProject\ProjectC#\C#\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe

一般模式用时钟进行侦听

C#
public partial class ServiceMain : ServiceBase { EventLog log = new EventLog(); System.Timers.Timer timer;//用这个时钟 public ServiceMain() { InitializeComponent(); this.AutoLog = false; if (!System.Diagnostics.EventLog.SourceExists("MySource")) { System.Diagnostics.EventLog.CreateEventSource("MySource", "MyLog"); } log.Source = "MySource"; log.Log = "MyLog"; timer = new System.Timers.Timer(); timer.Interval = 5000; timer.Elapsed += Timer_Elapsed; } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { //业务代码放在轮询中 //需要注意,一个轮询未结束下个又来 log.WriteEntry("Runing..."); } protected override void OnStart(string[] args) { log.WriteEntry("In OnStart."); timer.Enabled = true; } protected override void OnStop() { log.WriteEntry("In OnStop."); timer.Enabled = false; } }
C#
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TService { public class LogService { EventLog log = new EventLog(); public LogService() { if (!System.Diagnostics.EventLog.SourceExists("MySource")) { System.Diagnostics.EventLog.CreateEventSource("MySource", "MyLog"); } log.Source = "MySource"; log.Log = "MyLog"; } public void WriteEntry(string message) { log.WriteEntry(message); } } }

image.png

由于是异步时钟,可能出现一个过程没有完,下一个又来了,怎么处理?

loghelper类

C#
public class loghelper { public static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("loginfo");//这里的loginfo要与log4net.config中的loginfo一致 public static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("logerror");//这里的loginerror要与log4net.config中的logerror一致 public static void writelog(string info) { if (loginfo.IsInfoEnabled) { loginfo.Info(info); } } public static void writelog(Exception ex) { if (logerror.IsErrorEnabled) { logerror.Error(ex); } } }

log4net.config

XML
<log4net> <!--错误日志类--> <logger name="logerror"> <!--日志类的名字--> <level value="ALL" /> <!--定义记录的日志级别--> <appender-ref ref="ErrorAppender" /> <!--记录到哪个介质中去--> </logger> <!--信息日志类--> <logger name="loginfo"> <level value="ALL" /> <appender-ref ref="InfoAppender" /> </logger> <!--错误日志附加介质--> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <param name="Encoding" value="utf-8" /> <!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质--> <param name="File" value="Log\\LogError\\" /> <!--日志输出到exe程序这个相对目录下--> <param name="AppendToFile" value="true" /> <!--输出的日志不会覆盖以前的信息--> <param name="MaxSizeRollBackups" value="100" /> <!--备份文件的个数--> <param name="MaxFileSize" value="10240" /> <!--当个日志文件的最大大小--> <param name="StaticLogFileName" value="false" /> <!--是否使用静态文件名--> <param name="DatePattern" value="yyyyMMdd&quot;.html&quot;" /> <!--日志文件名--> <param name="RollingStyle" value="Date" /> <!--文件创建的方式,这里是以Date方式创建--> <!--错误日志布局--> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="&lt;HR COLOR=red&gt;%n异常时间:%d [%t] &lt;BR&gt;%n异常级别:%-5p &lt;BR&gt;%n异 常 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" /> </layout> </appender> <!--信息日志附加介质--> <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log\\LogInfo\\" /> <param name="AppendToFile" value="true" /> <param name="MaxFileSize" value="10240" /> <param name="MaxSizeRollBackups" value="100" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMdd&quot;.html&quot;" /> <param name="RollingStyle" value="Date" /> <!--信息日志布局--> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="&lt;HR COLOR=blue&gt;%n日志时间:%d [%t] &lt;BR&gt;%n日志级别:%-5p &lt;BR&gt;%n日 志 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" /> </layout> </appender> </log4net>

使用 BackgroundService 创建 Windows 服务

.NET Framework 开发人员可能熟悉 Windows 服务应用。 在 .NET Core 和 .NET 5+ 之前,依赖 .NET Framework 的开发人员可能会创建 Windows 服务来执行后台任务或执行长时间运行的进程。 此功能仍然可用,你可以创建作为 Windows 服务运行的辅助角色服务。

image.png

Nuget 安装 NLog.Extensions.Logging

在Program中注入NLog做日志管理

C#
using Microsoft.Extensions.DependencyInjection; using TestWorker; using NLog.Extensions.Logging; IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddHostedService<Worker>(); services.AddLogging(m => { m.AddNLog(); }); }) .Build(); await host.RunAsync();

下面三步启动服务

nlog.config

XML
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info" > <variable name="logDirectory" value="${basedir}/logs"/> <variable name="traceVal" value="${date:format=HH\:mm\:ss}|${message}"/> <variable name="layoutVal" value="${date:format=HH\:mm\:ss}|${uppercase:${level}}|${callsite:fileName=True}${newline}${message}${newline}${exception}"/> <variable name="consoleVal" value="${date:format=HH\:mm\:ss}|${pad:padding=5:inner=${level:uppercase=true}}|${message}"/> <targets> <target name="console" xsi:type="ColoredConsole" useDefaultRowHighlightingRules="false" layout="${consoleVal}" > <highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" /> <highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" /> <highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" /> <highlight-row condition="level == LogLevel.Error" foregroundColor="Red" /> <highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" /> </target> <target xsi:type="File" name="logfile" fileName="${logDirectory}/info_${shortdate}.log" keepFileOpen="false" layout="${layoutVal}" /> <target xsi:type="File" name="warnfile" fileName="${logDirectory}/warn_${shortdate}.log" keepFileOpen="false" layout="${layoutVal}" /> <target xsi:type="File" name="errfile" fileName="${logDirectory}/error_${shortdate}.log" keepFileOpen="false" layout="${layoutVal}" /> <target xsi:type="File" name="debugfile" fileName="${logDirectory}/debug_${shortdate}.log" keepFileOpen="false" layout="${layoutVal}" /> <target xsi:type="File" name="tracefile" fileName="${logDirectory}/trace_${shortdate}.log" keepFileOpen="false" layout="${traceVal}" /> </targets> <rules> <logger name="*" writeTo="console" /> <logger name="*" level="Trace" writeTo="tracefile"/> <logger name="*" level="Error" writeTo="errfile" /> <logger name="*" level="Warn" writeTo="warnfile"/> <logger name="*" minlevel="Info" writeTo="logfile"/> <logger name="*" minlevel="Debug" writeTo="debugfile" /> </rules> </nlog>

默认情况下,辅助角色服务模板不启用服务器垃圾回收 (GC)。 所有需要长时间运行服务的场景都应考虑此默认设置对性能的影响。 若要启用服务器 GC,将 ServerGarbageCollection 节点添加到项目文件:

XML
<ServerGarbageCollection>true</ServerGarbageCollection>
C#
namespace TestWorker { 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); } } public override Task StartAsync(CancellationToken cancellationToken) { return base.StartAsync(cancellationToken); } public override Task StopAsync(CancellationToken cancellationToken) { return base.StopAsync(cancellationToken); } } }

ExecuteAsync 每秒循环一次,记录当前日期和时间,直到进程收到取消信号。

C#
namespace TestWorker { public class Worker : BackgroundService { private readonly IHostApplicationLifetime _applicationLifetime; private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger, IHostApplicationLifetime applicationLifetime) { _logger = logger; _applicationLifetime = applicationLifetime; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); _applicationLifetime.StopApplication();//退出应用 } } public override Task StartAsync(CancellationToken cancellationToken) { return base.StartAsync(cancellationToken); } public override Task StopAsync(CancellationToken cancellationToken) { return base.StopAsync(cancellationToken); } } }

StopApplication 退出应用

为了与 .NET IHostedService 实现中的本机 Windows 服务互操作,需要安装 Microsoft.Extensions.Hosting.WindowsServices NuGet 包

image.png

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!