一、概念名称
Windows服务(即以前的 NT 服务),使您能够创建在它们自己的Windows会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这种服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。
二、创建Windows服务
2.1、创建项目
新建->项目->Windows 桌面->Windows 服务。
项目右键属性->应用程序->输出类型,可以看出它是属于"Windows 应用程序"。
2.2、添加安装程序
打开Service1.cs->空白处右键->添加安装程序。
2.3、设置安装信息
打开ProjectInstaller.cs。
2.3.1、serviceInstaller1
点击serviceInstaller1,在属性中设置服务信息,此示例是创建一个"HelloWorld"的服务。
说明:
Description:服务描述,直接显示到Windows服务列表中的描述。
DisplayName:服务显示名称,直接显示到Windows服务列表中的名称。
ServiceName:服务名称,启动或停止服务时的标识。
StartType:启动类型,如自动、手动等。
2.3.2、serviceProcessInstaller1
点击serviceProcessInstaller1,在属性中设置运行服务的账户类型。
2.4、生成项目
考虑到后面涉及到的Debugger调试方法,此处选择Release模式进行生成。
三、安装与卸载服务
3.1、InstallUtil.exe
在VS安装目录下将InstallUtil.exe拷贝到项目的Release文件夹下,InstallUtil.exe在VS2017的路径为:C:\Windows\Microsoft.NET\Framework\v4.0.30319。
3.2、安装服务
在Release文件夹的地址栏中输入"cmd"调出命令提示符窗体:
安装服务命令:
InstallUtil.exe LinkTo.Test.WindowsService.exe
启动服务命令:
net start HelloWorld
当然,一般我们使用批处理的方式来安装与卸载服务。
在Release文件夹下面,创建一个"安装服务.bat"的批处理文件:
@echo off echo=================================================== echo LinkTo.Test.WindowsService 正在安装服务 echo=================================================== @echo off InstallUtil.exe LinkTo.Test.WindowsService.exe @echo off echo=================================================== echo LinkTo.Test.WindowsService 正在启动服务 echo=================================================== @echo off net start HelloWorld pause
在运行中输入"services.msc"进入服务,即可看到新建的HelloWorld服务:
3.3、卸载服务
在Release文件夹下面,创建一个"卸载服务.bat"的批处理文件:
@echo off echo=================================================== echo LinkTo.Test.WindowsService 正在停止服务 echo=================================================== @echo off net stop HelloWorld @echo off echo=================================================== echo LinkTo.Test.WindowsService 正在卸载服务 echo=================================================== @echo off InstallUtil.exe /u LinkTo.Test.WindowsService.exe pause
四、服务定时器
一般来说,服务都会设置每隔多长时间执行一次任务,这里使用System.Threading.Timer来做个简单的日志记录,将日志写入到Release\Log文件夹下。
public partial class Service1 : ServiceBase { private static Timer timerAsync = null; private int dueTimeInterval = 1000 * 5; //单位:毫秒 private int periodInterval = 1000 * 5; //单位:毫秒 public Service1() { InitializeComponent(); //callback:一个 TimerCallback 委托,表示要执行的方法。 //state:一个包含回调方法要使用的信息的对象,或者为空引用。 //dueTime:调用 callback 之前延迟的时间量(以毫秒为单位)。指定 Timeout.Infinite 以防止计时器开始计时,指定零(0)以立即启动计时器。 //period:调用 callback 的时间间隔(以毫秒为单位)。指定 Timeout.Infinite 可以禁用定期终止。 timerAsync = new Timer(AutoAsyncCallback, null, Timeout.Infinite, Timeout.Infinite); } /// /// 服务启动 /// /// protected override void OnStart(string[] args) { base.OnStart(args); timerAsync.Change(dueTimeInterval, periodInterval); WriteLog(DateTime.Now.ToString("HH:mm:ss") + " 服务启动" + "\r\n"); WriteLog(Environment.NewLine); } /// /// 服务停止 /// protected override void OnStop() { base.OnStop(); if (timerAsync != null) { timerAsync.Change(Timeout.Infinite, Timeout.Infinite); timerAsync.Dispose(); timerAsync = null; } WriteLog(DateTime.Now.ToString("HH:mm:ss") + " 服务停止" + "\r\n"); WriteLog(Environment.NewLine); } /// /// 服务暂停 /// protected override void OnPause() { base.OnPause(); WriteLog(DateTime.Now.ToString("HH:mm:ss") + " 服务暂停" + "\r\n"); WriteLog(Environment.NewLine); } /// /// 计算机关闭 /// protected override void OnShutdown() { base.OnShutdown(); WriteLog(DateTime.Now.ToString("HH:mm:ss") + " 计算机关闭" + "\r\n"); WriteLog(Environment.NewLine); } /// /// 回调函数 /// /// private void AutoAsyncCallback(object state) { try { timerAsync.Change(Timeout.Infinite, Timeout.Infinite);#if DEBUG if (!Debugger.IsAttached) Debugger.Launch(); //当进程运行到这里的时候会自动停下来并弹出提示框 Debugger.Break(); //这个方法和在VS中加入红色的断点是一模一样的#endif WriteLog(DateTime.Now.ToString("HH:mm:ss") + " AutoAsyncCallback执行开始,线程ID = " + Thread.CurrentThread.ManagedThreadId + "\r\n"); Thread.Sleep(1000 * 10); //模拟耗时较长的计算任务,且耗时大于定时的间隔时间。 } catch (Exception ex) { WriteLog(DateTime.Now.ToString("HH:mm:ss") + " AutoAsyncCallback执行异常:" + "\r\n" + ex.Message); } finally { timerAsync.Change(dueTimeInterval, periodInterval); WriteLog(DateTime.Now.ToString("HH:mm:ss") + " AutoAsyncCallback执行结束" + "\r\n"); WriteLog(Environment.NewLine); } } /// /// 日志记录 /// /// 日志信息 void WriteLog(string logInfo) { try { string logDirectory = AppDomain.CurrentDomain.BaseDirectory + "\\Log"; if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } string filePath = logDirectory + "\\" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt"; File.AppendAllText(filePath, logInfo); } catch { } } }
Service1.cs
五、调试服务
由于Windows服务程序不能直接执行,所以不能直接打断点进行调试。调试服务的常用方式有以下两种:
5.1、附加到进程
服务启动后,点击调试->附加到进程->选择LinkTo.Test.WindowsService->附加。
5.2、Debugger
#if DEBUG if (!Debugger.IsAttached) Debugger.Launch(); //当进程运行到这里的时候会自动停下来并弹出提示框 Debugger.Break(); //这个方法和在VS中加入红色的断点是一模一样的#endif
使用Debugger代码进行调试,在项目生成的时候,需使用Release模式,否则一直会有附加提示,可在配置管理器中修改Release模式。