原文地址: 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.

        源码:

Get Code Download

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的更多相关文章

  1. Windows Azure Cloud Service (36) 在Azure Cloud Service配置SSL证书

    <Windows Azure Platform 系列文章目录> 在某些时候,我们需要在Azure PaaS Cloud Service配置HTTPS连接.本章将介绍如何在本地创建证书,然后 ...

  2. 当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Frame

    异常详细信息: System.Web.HttpException: 当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Fra ...

  3. C#制作Windows service服务系列二:演示一个定期执行的windows服务及调试(windows service)

    系列一: 制作一个可安装.可启动.可停止.可卸载的Windows service(downmoon原创) 系列二:演示一个定期执行的windows服务及调试(windows service)(down ...

  4. Windows Service的安装卸载 和 Service控制(转)

    Windows Service的安装卸载 和 Service控制 原文地址:http://www.cnblogs.com/Peter-Zhang/archive/2011/10/15/2212663. ...

  5. Windows Service的安装卸载 和 Service控制

    原文 Windows Service的安装卸载 和 Service控制 本文内容包括如何通过C#代码安装Windows Service(exe文件,并非打包后的安装文件).判断Service是否存在. ...

  6. Windows as a Service(1)—— Windows 10服务分支

    前言 作为公司的IT管理员,管理全公司Windows 10操作系统的更新一直是工作中的头疼之处.微软提供了很多方法来帮助我们管理公司的Windows 10更新,比如Windows Server Upd ...

  7. [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 ...

  8. Windows as a Service(1)—— Windows 10服务分支

    前言 作为公司的IT管理员,管理全公司Windows 10操作系统的更新一直是工作中的头疼之处.微软提供了很多方法来帮助我们管理公司的Windows 10更新,比如Windows Server Upd ...

  9. 如何启动Service,如何停用Service(转)

    如何启用Service,如何停用Service Android中的服务和windows中的服务是类似的东西,服务一般没有用户操作界面,它运行于系统中不容易被用户发现,可以使用它开发如监控之类的程序.服 ...

  10. 您在基于 Windows 7 的或基于 Windows Server 2008 R2 的计算机上读取器中插入智能卡时出现错误消息:"设备驱动程序软件未能成功安装"

    http://support.microsoft.com/kb/976832/zh-cn http://support.microsoft.com/kb/976832/zh-tw 症状 当智能卡插入智 ...

随机推荐

  1. 前端编码规范之CSS

    "字是门面书是屋",我们不会去手写代码,但是敲出来的代码要好看.有条理,这还必须得有一点约束~ 团队开发中,每个人的编码风格都不尽相同,有时候可能存在很大的差异,为了便于压缩组件对 ...

  2. HappyAA服务器部署笔记2(nginx的静态资源缓存配置)

    我近期对服务器进行了少量改进,虽然之前使用了nginx反向代理之后性能有所提高,但仍然不够,需要使用缓存来大幅度提高静态资源的访问速度. 服务器上的静态资源主要有这些:png, jpg, svg, j ...

  3. alias指令:设置命令别名

    alias: usage: alias [-p] [name[=value] ... ] 1. 语法    alias [参数][命令别名]=[原命令名称]   2. 功能介绍 该指令主要用于为原命令 ...

  4. 玩转动态编译 - 高级篇:一,IL访问静态属性和字段

    IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET ...

  5. Azure PowerShell (10) 使用PowerShell导出订阅下所有的Azure VM和Cloud Service的高可用情况

    <Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China服务. 该脚本下载地址在http://files.cnblogs.co ...

  6. 用FlexGrid做开发,轻松处理百万级表格数据

    表格数据处理是我们项目开发中经常会遇到的设计需求之一,所需处理的数据量也较大,通常是万级.甚至百万级.此时,完全依赖平台自带的表格工具,往往无法加载如此大的数据量,或者加载得很慢影响程序执行. 那么, ...

  7. OAuth2 Backend Web Application 验证过程

    本文是从我的 github 博客转载的,原文请看. 一图胜千言.图片请自由转载,请保留图片的原始签名.

  8. Base 64 编码

    原创地址:http://www.cnblogs.com/jfzhu/p/4020097.html 转载请注明出处 (一)Encoding VS. Encryption 很多人都以为编码(Encodin ...

  9. 每天一个linux命令(46):vmstat命令

    vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存.进程.CPU活动进行监控.他是对系统的整体情况进行统计,不足之处是无法对某个进程进行深 ...

  10. HTML5_02之视频、音频、Canvas

    1.HTML5新特性之视频播放--video: ①例:<video src=""></video> ②video标签默认为300*150的inline-bl ...