Windows Service--Write a Better Windows Service
原文地址: http://visualstudiomagazine.com/Articles/2005/10/01/Write-a-Better-Windows-Service.aspx?Page=1
Writing a Windows service is significantly more involved than many
authors would have you believe. Here are the tools you need to create a
Windows service robust enough for the real world.
- 源码:
Most programmers know that you typically implement Windows services as executable programs. What many developers don't realize is that a single executable can contain more than one service. For example, the standard Windows intetinfo.exe process hosts several services, including IIS Admin, World Wide Web Publishing, and Simple Mail Transfer Protocol (SMTP).
All services hosted by one executable and configured to use the same Log On account run in the same process. The first service to start launches the executable, and the last service to stop causes the process to exit. If you configure the services using different Log On properties—say, service X runs as John, service Y runs as Mary, and both services are implemented in the same executable—you end up starting a separate process for each user identity of the launched services.
A single process can contain more than one Windows service, and each service can perform several tasks (see Figure 1). For example, an online shop can use a service to send notifications about shipped products, payment collections, and weekly promotions. You can implement these operations as different services, or you can run them as different threads of the same service. A common approach is to implement logically related tasks as multiple threads of a single service and non-related tasks as different services.
If you read a typical article explaining how to write a Windows service in C# or Visual Basic .NET, you get the impression that building Windows services is easy. Simply pick the Windows Service project template, follow the instructions provided in the MSDN documentation or online tutorials, add code to implement the application logic, and voilà, the service is ready. Unfortunately, many developers discover only after the fact that the Windows services they create using this approach are hard to manage.
For one, you can't debug your service by pressing the F5 key from the Visual Studio .NET IDE. After you figure out how to launch the service executable from command line, debug messages won't appear if you try to display them by calling Console.Write or MessageBox.Show. You also get an error if you install the service using a Windows installer (MSI) package and then attempt to deploy a hot fix by executing an updated MSI file in repair mode. Finally, if your service performs multiple tasks running at scheduled times or timed intervals, you need to design all aspects of your solution, including the timing and multithreading. Faced with these issues, many developers turn to the Web and newsgroups to slog through the issues one-by-one. That brute-force method can work, after a fashion, but a far better solution is to avoid all these problems in the first place.
This article comes with two code samples written in C#. The first sample contains a project to build a library (My.Utilities.dll), which provides classes for implementing easy-to-use Windows services. The SampleService project illustrates how you can incorporate this library in a project that implements several Windows services, each performing multiple tasks. There is also a sample setup project (SampleSetup), which shows how to implement the Windows service installer.
The My.Utilities library code sample that accompanies this article includes several classes for building Windows service hosts, services, and threads (see Table 1). All classes related to Windows services belong to the My.Utilities.Services namespace.
Implement Service Processes
You can use the WindowsService class, which extends ServiceBase, to implement service processes and define properties of Windows services. When you derive a service process from WindowsService, you can debug it directly from the Visual Studio .NET IDE. It lets you execute the process from command line, display debug messages, and perform self-installation. This class has several helpful members, but you need to be aware of only a few of them.
The static IsInteractive property is a simple wrapper for the obscure UserInteractive member of the Framework's System.Environment class. The service process can use it to determine whether it was launched by Service Control Manager (SCM) or an interactive user (for example, during debugging).
The overloaded Run method uses different mechanisms to execute code depending on the value of the IsInteractive property. If the value is false, it simply calls the Run method of the ServiceBase class; otherwise, it calls the Start method of each hosted service (see Listing 1).
When called from ServiceBase-derived classes, the .NET Framework's Console.Write and MessageBox.Show do not display any output. You can display debug messages during interactive execution by using the static ShowMessageBox method, which I implemented by reverse-engineering the private LateBoundMessageBoxShow method of the ServiceBase class.
Be sure to add your code to the Start and Stop methods if you implement the main operations in a class derived from WindowsService rather than using the OnStart and OnStop event handlers. It's even better to isolate the business logic in the dedicated worker threads.
A WindowsService object keeps track of the worker threads assigned to it through the WorkerThreads property, which contains an array of objects derived from the WorkerThread class. In addition to several helper members, WorkerThread declares two abstract methods: Start and Stop. When you launch a Windows service, it iterates through the worker threads and invokes the Start method on each of them. Similarly, it calls the Stop method after receiving a signal to stop (see Listing 2).
You derive your own worker thread class from WorkerThread by adding the initialization and business logic to the Start routine and using the Stop method to implement the clean-up procedure. WorkerThread doesn't offer much functionality, but several classes derived from it do. These classes simplify the implementation of the timer-based operations.
If your Windows service performs operations at scheduled times or timed intervals, you can base them on TimerThread, DailyThread, or WeeklyThread (see Figure 2). The TimerThread class executes the Run method at timed intervals defined through the Interval property. If the execution time of the Run method exceeds the specified interval, the thread keeps skipping the scheduled execution and waiting for the next interval until the Run method completes.
If your service contains several threads executing at the same or close intervals, you might not want to fire all of them at the same time. You can make sure that the threads start at different times by setting their initial Delay properties to different values.
Execute Daily Tasks
You execute DailyThread and WeeklyThread once per day. DailyThread and WeeklyThread use the execution time of the day defined in the DailyExecutionTime property. The DailyExecutionTime property accepts a string value in the 24-hour format, such as "18:30." By default, the worker thread classes use GMT (UTC) for time-related operations, but you can change it by setting the value of the UseLocalTime property.
DailyThread assumes that you must perform the given task every day, while WeeklyThread can run on the specified days of the week. For example, your service might need to send notifications to company employees on Wednesdays or workdays only. Use the DaysOfExecution property to specify the days you want to perform an operation. This property takes a bitmask value defined in the DaysOfWeek enumerated type.
You don't typically need to follow a rigid execution schedule for classes derived from TimerThread, but it's critical for daily and weekly operations to be performed once and only once per day. If a service that sends daily notifications at 6 a.m. was down from 5:55 a.m. to 6:05 a.m., you would probably want to send the notification as soon as the service starts at 6:06 a.m.. On the other hand, if the service was down from 5:55 a.m. to 5:50 a.m. of the next day, it might make sense to wait for the next execution at 6 a.m. instead of sending two notifications within five minutes.
When a daily (or weekly) thread starts, it checks the current time and compares it with the scheduled execution time using the 24-hour clock. If the execution time is in the past (for example, execution time is 6 a.m. and current time is 7 a.m.), the execution is scheduled for the next day. In the meantime, the thread keeps waking up after the interval defined in the WakeUpInterval property to check whether it must call the Run method. The thread executes the Run method after it reaches the scheduled execution time or if it detects that the last operation was performed more than a day ago and the next execution is not scheduled within the next 12 hours. Note that the WeeklyThread class also takes into account days of the week the task should execute. The thread resets the LastExecutionTime property after executing the Run method successfully.
There is one problem with this approach. If the service stops (say the server crashes), you lose the information about the last execution time. You can preserve the last execution time in a persistent location (such as a database table or text file) by overriding the SetLastExecutionTime and GetLastExecutionTime methods.
Build a Windows Service
Building a Windows service requires several steps. Begin by creating a new project using the Windows Service template, then add a reference to the utilities library (My.Utilities.dll). Next, include a reference to the My.Utilities.Services namespace in your source code files (where needed), and change the base class for your Windows service process from ServiceBase to WindowsService. These steps are all straightforward.
The next few steps get a little trickier, so I'll illustrate them using a sample Windows service. You implement the operation performed by your services in the worker thread classes derived from WorkerThread. If you need to perform an operation at scheduled times or at timed intervals, simply derive the worker thread from TimerThread, DailyThread, or WeeklyThread, and override the Run method.
The SampleService project performs three tasks executed by two Windows services. The first service sends an e-mail notification to the users whose passwords are about to expire once per day, except during the weekend. The second service queries an external data source every two minutes and copies any new users it finds to the local database. It also applies updates to the existing users stored in the local database every five minutes.
You can view these tasks as three distinct operations, so the solution splits them into separate worker thread classes: PasswordCheckThread (derived from WeeklyThread), NewUserThread, and UserUpdateThread. You derive the latter two worker threads from TimerThread.
The worker thread classes implement the business logic of the designated operations by overriding the Run methods. Note that I kept the code simple by making the Run methods in the sample classes write only informational messages to the application log file using a static helper method of the service host. The sample illustrates a case when an operation takes longer to complete than expected by forcing NewUserThread and UserUpdateThread to sleep at random intervals. Note that the worker threads don't contain any initialization logic or data, such as execution frequency or initial delay. The server process performs these tasks.
The Main function of the service process handles initialization and invocation of the hosted services and worker threads. Implementing the service process takes only a few steps. Create a Windows Service project, then rename the wizard-generated Service1 class (and file) to something more meaningful, such as ServiceHost. Next, change the Main function definition to include the command-line parameters (you can also delete the function contents):
using My.Utilities.Services; ... // Derive service host from WindowsService // instead of ServiceBase. public class ServiceHost: WindowsService { ... private static void Main ( string[] args ) { } }
In Main, create objects for each worker thread that you have implemented already and initialize their runtime properties:
NewUserThread newUserThread = new NewUserThread(); newUserThread.Name = "New User Check"; newUserThread.Delay= ; newUserThread.Interval = * * ;
You can also pass initialization parameters through overloaded constructors, but you need to implement those as well. You might want to store these values in a configuration file in a real application.
Create the Service Objects
After you initialize the worker threads, create the Windows service objects and define their properties. At a minimum, you need to specify the service name and assign the worker threads to it:
WindowsService userCheckService = new WindowsService(); userCheckService.ServiceName = "Test Service: User Check"; userCheckService.WorkerThreads = new WorkerThread[] { newUserThread, userUpdateThread };
Finally, create an array of the ServiceBase objects holding the
initialized services and execute the overloaded Run method passing the
array and optional command-line arguments to it:
ServiceBase[] services = new ServiceBase[] { userCheckService, emailService }; Run(services, args);
The Run method implemented by WindowsService lets you execute the service manually. Note that this method is different from the one implemented in the ServiceBase class, which takes one parameter instead of two.
At this point, you must be able to debug your services or execute them from the command prompt. For testing purposes, set breakpoints or add code to display a debug message using WindowsService.ShowMessageBox in the Run methods of the worker threads:
override protected void Run ( object source ) { WindowsService.ShowMessageBox( "Executing {0}", Name); ... }
Add the installer class to the service project to install and uninstall your service. Rename the class and file to something meaningful such as WindowsServiceInstaller, and make sure that you derive it from System.Configuration.Install.Installer. The class also needs the RunInstaller attribute:
using System.Configuration.Install; using System.ServiceProcess; ... [RunInstaller(true)] public class WindowsServiceInstaller : Installer { ... }
Use the class constructor to implement the initialization logic (see Listing 3). I don't like the fact that this code hard-codes the name and display name of the services because you also use the names of the services in the Main function of the service host. Remember to change the string literals in several places if you ever decide to rename a service; otherwise, the service will fail. You can avoid this problem by defining and referencing the names of the services using static properties:
public class ServiceHost: WindowsService { internal static string UserCheckServiceName = "Sample Service: User Check"; internal static string EmailServiceName = "Sample Service: Email Notifier"; ... }
The sample code uses identical values for both the service name and its display name, but they don't have to be the same. You must also be aware that the Account property of a ServiceInstaller object that is assigned the value of ServiceAccount.User (instead of ServiceAccount.LocalSystem) will cause the setup process to fail if the user running the installer mistypes the account name or password.
Create the Setup File
You can install the services after you implement the installer class. A typical approach is to use the .NET Framework's Installer Tool (InstallUtil.exe). I'll skip this option because it's been covered extensively elsewhere. Instead, I'll describe two alternatives: incorporating the installer in an MSI package and creating a self-installer.
You can enable the MSI package to install your services by defining a custom action and associating it with the service executable. If you use the Setup Project template in Visual Studio .NET, simply switch to the Custom Actions view, and add a new custom action to the Install, Commit, Rollback, and Uninstall events, associating them with the service executable (see Figure 3). The Services Control Panel displays your services after you build the MSI file and run the setup.
You still need to address one important problem. If you fix a bug in the service code and want to deploy it by executing the modified setup package in the repair mode, the setup fails. You need to reinstall the application to deploy the fix. You can use one of several alternatives if this becomes a hassle (and it will).
One option is to add code to the service installer constructor to detect whether the relevant services are installed already (you can use helper methods exposed by the WindowsService class for this). If the services are installed, the logic won't create the corresponding installer objects, effectively making it a no-op. You can achieve the same effect by setting the Condition field of the custom action associated with the Install event to NOT REINSTALL (case-sensitive) (see Figure 4).
You can enable self-installation in a Windows service host class derived from WindowsService by adding this block of code at the beginning of the Main method (the first parameter of the InstallOrUninstall method expects the command-line switches; the second parameter must contain an instance of your service installer class):
public class ServiceHost: WinodwsService { ... private static void Main ( string[] args ) { if (InstallOrUninstall(args, new WindowsServiceInstaller())) { return; } ... } }
Inserting this code and executing the program from command prompt with the /i switch installs the services, while the /u switch uninstalls them.
I've documented the source code extensively, so take a look at the code comments if you want to learn how the functionality described in this article is implemented in the utilities library. You can also check the provided help file located under the library project folder; it describes several features not mentioned in this article.
Windows Service--Write a Better Windows Service的更多相关文章
- Windows Azure Cloud Service (36) 在Azure Cloud Service配置SSL证书
<Windows Azure Platform 系列文章目录> 在某些时候,我们需要在Azure PaaS Cloud Service配置HTTPS连接.本章将介绍如何在本地创建证书,然后 ...
- 当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Frame
异常详细信息: System.Web.HttpException: 当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Fra ...
- C#制作Windows service服务系列二:演示一个定期执行的windows服务及调试(windows service)
系列一: 制作一个可安装.可启动.可停止.可卸载的Windows service(downmoon原创) 系列二:演示一个定期执行的windows服务及调试(windows service)(down ...
- Windows Service的安装卸载 和 Service控制(转)
Windows Service的安装卸载 和 Service控制 原文地址:http://www.cnblogs.com/Peter-Zhang/archive/2011/10/15/2212663. ...
- Windows Service的安装卸载 和 Service控制
原文 Windows Service的安装卸载 和 Service控制 本文内容包括如何通过C#代码安装Windows Service(exe文件,并非打包后的安装文件).判断Service是否存在. ...
- Windows as a Service(1)—— Windows 10服务分支
前言 作为公司的IT管理员,管理全公司Windows 10操作系统的更新一直是工作中的头疼之处.微软提供了很多方法来帮助我们管理公司的Windows 10更新,比如Windows Server Upd ...
- [Windows Azure] What is a cloud service?
What is a cloud service? When you create an application and run it in Windows Azure, the code and co ...
- Windows as a Service(1)—— Windows 10服务分支
前言 作为公司的IT管理员,管理全公司Windows 10操作系统的更新一直是工作中的头疼之处.微软提供了很多方法来帮助我们管理公司的Windows 10更新,比如Windows Server Upd ...
- 如何启动Service,如何停用Service(转)
如何启用Service,如何停用Service Android中的服务和windows中的服务是类似的东西,服务一般没有用户操作界面,它运行于系统中不容易被用户发现,可以使用它开发如监控之类的程序.服 ...
- 您在基于 Windows 7 的或基于 Windows Server 2008 R2 的计算机上读取器中插入智能卡时出现错误消息:"设备驱动程序软件未能成功安装"
http://support.microsoft.com/kb/976832/zh-cn http://support.microsoft.com/kb/976832/zh-tw 症状 当智能卡插入智 ...
随机推荐
- 元素的click与dblclick
JavaScript与HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间,是用户或浏览器自身执行的某种动作.诸如click.load.mousemover,都是事 ...
- Javascript分号,加还是不加?
关于这个问题,网上已经有很多人讨论过了,我先说说自己对这个问题的回答:加!(但非必须) 有些人写代码,懒得加分号,除非是迫不得已才勉强放一个分号上去.如果你可以保证你写的代码不出现任何 bug,那当然 ...
- C# Random生成多个不重复的随机数万能接口
C#,Radom.Next()提供了在一定范围生成一个随机数的方法,我现在有个业务场景是给其他部门推送一些数据供他们做抽样检查处理,假设我的数据库里面有N条数据,现在要定期给其随机推送数据,我需要先拿 ...
- madplay播放控制
管理madplay的主程序,包括播放,暂停播放,恢复播放,停止播放system("madplay north.mp3 &");//利用system函数调用madplay播放 ...
- 招聘.NET开发人员(截止于2015-06-15)
文章更新 2015-06-15 01:00AM: 感谢各位的支持,简历和解决方案接收截止.2015-06-08 08:30AM: 已经收到一些简历和解决方案,正在筛选中.职位仍然开放,欢迎发送简历及解 ...
- 深入挖掘.NET序列化机制——实现更易用的序列化方案
.NET框架为程序员提供了“序列化和反序列化”这一有力的工具,使用它,我们能很容易的将内存中的对象图转化为字节流,并在需要的时候再将其恢复.这一技术的典型应用场景包括[1] : 应用程序运行状态的持久 ...
- WCF basicHttpBinding之Transport Security Mode, clientCredentialType="None"
原创地址:http://www.cnblogs.com/jfzhu/p/4071342.html 转载请注明出处 前面文章介绍了<WCF basicHttpBinding之Message Sec ...
- 谈谈php里的IOC控制反转,DI依赖注入
理论 发现问题 在深入细节之前,需要确保我们理解"IOC控制反转"和"DI依赖注入"是什么,能够解决什么问题,这些在维基百科中有非常清晰的说明. 控制反转(In ...
- .Net 转战 Android 4.4 日常笔记(1)--工具及环境搭建
闲来没事做,还是想再学习一门新的技术,无论何时Android开发比Web的开发工资应该高40%,我也建议大家面对移动开发,我比较喜欢学习最新版本的,我有java的基础,但是年久,已经淡忘,以零基础学习 ...
- 1、NoSQL概述
最近抽时间把Redis学了一下,所以就在网上找了一些资料.然后找到尚硅谷-周阳老师的视频教程,觉得里面的讲的挺好.所以就把他视频当中的资料教程整理出来. 单机MySQL的美好时代 在90年代,一个网站 ...