Microsoft Windows 服务(过去称为 NT 服务)允许用户创建可在其自身的 Windows 会话中长时间运行的可执行应用程序。
这些服务可在计算机启动时自动启动,可以暂停和重启,并且不显示任何用户界面。 这些功能使服务非常适合在服务器上使用,或者需要长时间运行的功能(不会影响在同一台计算机上工作的其他用户)的情况。
在创建和生成应用程序之后,可以通过运行命令行实用程序 InstallUtil.exe 并将该路径传递给服务的可执行文件来安装它。 然后,可以使用服务控制管理器 来启动、停止、暂停、恢复和配置服务。
服务生存期
服务可以三种基本状态之一存在:Running、Paused 或 Stopped。 该服务还可以报告挂起命令的状态:ContinuePending、PausePending、StartPending 或 StopPending。
新建一个项目
属性
Property | 设置 |
---|---|
CanStop | True 表示服务将接受请求停止运行;false 将阻止服务被停止。 |
CanShutdown | True 表示当服务所在的计算机关机时服务需要接受通知,启用它来调用 OnShutdown 过程。 |
CanPauseAndContinue | True 表示服务将接受请求暂停或恢复运行;false 将阻止服务被暂停或恢复。 |
CanHandlePowerEvent | True 表示服务可处理计算机电源状态更改的通知;false 将阻止向服务通知这些更改。 |
AutoLog | True 将在你的服务执行操作时向应用程序事件日志写入信息条目;false 将禁用该功能。 有关详细信息,请参阅如何:记录关于服务的信息。 注意:默认情况下,将 AutoLog 设置为 true 。 |
修改ServiceMain代码编辑器,并填写你想要对 OnStart 和 OnStop 过程的处理。
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;
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.");
}
}
右键添加安装器
选中ServiceInstaller
属性
DelayedAutoStart | 获取或设置一个值,该值指示是否应延迟启动该服务,直到运行其他自动启动的服务。 |
Description | 获取或设置服务的说明。 |
DesignMode | 获取一个值,用以指示 Component 当前是否处于设计模式。 (继承自 Component) |
DisplayName | 指示向用户标识服务的友好名称。 |
Events | 获取附加到此 Component 的事件处理程序的列表。 (继承自 Component) |
HelpText | 获取安装程序集合中所有安装程序的帮助文字。 (继承自 Installer) |
Installers | 获取该安装程序包含的安装程序的集合。 (继承自 Installer) |
Parent | 获取或设置包含该安装程序所属的集合的安装程序。 (继承自 Installer) |
ServiceName | 指示系统用于标识此服务的名称。 此属性必须与要安装的服务的 ServiceName 相同。 |
ServicesDependedOn | 指示为使该服务能够运行而必须正在运行的服务。 |
StartType | 指示启动此服务的方式和时间。 |
修改服务运行账号
安装服务
textinstallutil D:\MyProject\ProjectC#\C#\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe
点击启动
查看日志文件
删除服务
textinstallutil /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);
}
}
}
由于是异步时钟,可能出现一个过程没有完,下一个又来了,怎么处理?
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".html"" />
<!--日志文件名-->
<param name="RollingStyle" value="Date" />
<!--文件创建的方式,这里是以Date方式创建-->
<!--错误日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="<HR COLOR=red>%n异常时间:%d [%t] <BR>%n异常级别:%-5p <BR>%n异 常 类:%c [%x] <BR>%n%m <BR>%n <HR Size=1>" />
</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".html"" />
<param name="RollingStyle" value="Date" />
<!--信息日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="<HR COLOR=blue>%n日志时间:%d [%t] <BR>%n日志级别:%-5p <BR>%n日 志 类:%c [%x] <BR>%n%m <BR>%n <HR Size=1>" />
</layout>
</appender>
</log4net>
使用 BackgroundService
创建 Windows 服务
.NET Framework 开发人员可能熟悉 Windows 服务应用。 在 .NET Core 和 .NET 5+ 之前,依赖 .NET Framework 的开发人员可能会创建 Windows 服务来执行后台任务或执行长时间运行的进程。 此功能仍然可用,你可以创建作为 Windows 服务运行的辅助角色服务。
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();
下面三步启动服务
Worker
类添加为托管服务。host
实例上调用 Run
。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 包。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!