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.

        源码:

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):

 1 using My.Utilities.Services;
 2 ...
 3 // Derive service host from WindowsService
 4 // instead of ServiceBase.
 5 public class ServiceHost:
 6    WindowsService
 7 {
 8 ...
 9 private static void Main
10 (
11    string[] args
12 )
13 {
14 }
15 }

In Main, create objects for each worker thread that you have implemented already and initialize their runtime properties:

1 NewUserThread newUserThread =
2    new NewUserThread();
3
4 newUserThread.Name = "New User Check";
5 newUserThread.Delay= 0;
6 newUserThread.Interval   = 2 * 60 * 1000;

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:

 1 WindowsService userCheckService =
 2    new WindowsService();
 3 userCheckService.ServiceName =
 4    "Test Service: User Check";
 5
 6 userCheckService.WorkerThreads =
 7    new WorkerThread[]
 8       {
 9          newUserThread,
10          userUpdateThread
11       };

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:

1 ServiceBase[] services =
2    new ServiceBase[]
3       {
4          userCheckService,
5          emailService
6       };
7 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:

 1 override protected void Run
 2 (
 3    object source
 4 )
 5 {
 6    WindowsService.ShowMessageBox(
 7       "Executing {0}",
 8       Name);
 9    ...
10 }

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:

1 using System.Configuration.Install;
2 using System.ServiceProcess;
3 ...
4 [RunInstaller(true)]
5 public class WindowsServiceInstaller :
6    Installer
7 {
8    ...
9 }

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:

 1 public class ServiceHost: WindowsService
 2 {
 3 internal static string
 4       UserCheckServiceName =
 5       "Sample Service: User Check";
 6
 7    internal static string
 8       EmailServiceName   =
 9       "Sample Service: Email Notifier";
10    ...
11 }

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):

 1 public class ServiceHost:
 2    WinodwsService
 3 {
 4 ...
 5 private static void Main
 6 (
 7    string[] args
 8 )
 9 {
10    if (InstallOrUninstall(args,
11       new WindowsServiceInstaller()))
12    {
13       return;
14    }
15    ...
16 }
17 }

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.

时间: 2024-10-18 00:14:52

Windows Service--Write a Better Windows Service的相关文章

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

系列一: 制作一个可安装.可启动.可停止.可卸载的Windows service(downmoon原创) 系列二:演示一个定期执行的windows服务及调试(windows service)(downmoon) 系列三: windows service系列三--制作可控制界面的windows service 一.经常有人问起如何让程序定期自动执行? 除了像系统任务和SQL JOB/DTS等都可以满足不同的用户需求外,这里演示了如何做一个简单的windows serivce的框架.主要的功能是按照

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

<Windows Azure Platform 系列文章目录> 在某些时候,我们需要在Azure PaaS Cloud Service配置HTTPS连接.本章将介绍如何在本地创建证书,然后使用HTTPS连接Azure Cloud Service. 1.创建证书 以管理员身份运行CMD,使用Makecert命令,安装Azure证书.具体的命令如下: makecert -sky exchange -r -n "CN=<CertificateName>" -pe -a

Web Service学习-CXF开发Web Service实例demo(一)

Web Service是什么? Web Service不是框架.更甚至不是一种技术. 而是一种跨平台,跨语言的规范 Web Service解决什么问题: 为了解决不同平台,不同语言所编写的应用之间怎样调用问题.比如.有一个C语言写的程序.它想去调用java语言写的某个方法. 集中解决:1,远程调用 2.跨平台调用 3,跨语言调用 实际应用: 1.同一个公司的新,旧系统的整合.Linux上的java应用,去调用windows平台的C应用 2,不同公司的业务整合.业务整合就带来不同公司的系统整合.不

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

http://support.microsoft.com/kb/976832/zh-cn http://support.microsoft.com/kb/976832/zh-tw 症状 当智能卡插入智能卡阅读器后时,Windows 尝试下载并安装智能卡 minidrivers 通过插服务卡.如果自定义的加密服务提供程序未在系统上安装智能卡的驱动程序在任一预配置位置,如 Windows 更新. WSUS 或 intranet 路径不可用,在通知区域中将收到以下错误消息: 未能成功安装设备驱动程序软

【Advanced Windows Phone Programming】在windows phone 8中解码mp3 和编码pcm

转眼间不做wp开发,投身于php事业已然一年了,转身看到8.1的发布,俨然一片欣欣向荣的景象,但是开发社区却没比一年前有过多大的提高,这并不是一个好现象,遂在git上开源了之前音频处理库,希望能对社区有所贡献,地址如下:https://github.com/sandcu/wpaudio 觉得有用的同学请在git上点个星,好让更多的同学看到,下面开始正文. 用到的三方的库主要有两个,一个是mad一个是lame,lame的编译请参考上篇文章,mad直接编译即可,工程rebuild的过程是先编译lib

Windows Server 2012R2 WDS部署Windows 7

windows 部署服务 WDS即Windows Deployment Service 类似于Linux的PXE+Kickstart 安装完Windows Server 2012R2后添加DHCP服务器.Windows部署服务.批量激活服务角色 DHCP服务配置 WDS服务配置 开启客户机进行安装Windows 7 下图中用户名需要输入全(WDS-fudanwuxi\administrator) 原文地址:http://blog.51cto.com/jschinamobile/2159989

破解 Windows 和 Office;激活 Windows 和 Office

1 功能 破解 Windows 和 Office:激活 Windows 和 Office.2 界面 3 下载 https://pan.baidu.com/share/home?uk=15974437204 返回博客首页 https://www.cnblogs.com/apsoft

Liam的C# 学习历程(七):WPF(Windows Presentation Foundation)、Windows Form Applications

在今天的课堂中,老师向我们讲述了关于一些WPF(Windows Presentation Foundation)和Windows Form Applications的内容,接下来就让我们一起来复习一下: (一).WPF(Windows Presentation Foundation): WPF是一个重要运用于desktop手机开发方面.它使用到了一种XML的变形语言——XAML的语言(eXtensible Application Markup Language). 使用XAML开发人员可以对WP

windows安装程序无法将windows配置为在此计算机的硬件上运行

关于装windows系统时,出现一些安装中断的处理 该方法适用于 windows安装程序无法将windows配置为在此计算机的硬件上运行 计算机意外地重新启动或遇到错误. Windows 安装无法继续.若要安装Windows,请单击“确定”重新启动计算机,然后重新启动安装”. 按下shift+f10 会打开命令窗口, 进入到C:\windows\system32\oobe文件夹, 输入msoobe回车然后输入msoobe即可进入下一步操作, 但错误提示框仍然在,不用理会按照屏幕提示输入相应的信息

Windows Server Technical Preview(Windows Server 10)安装体验

10月1日对于中国人来说是个非常大的节日,它不仅是祖国的生日,也是七天小长假的开始,也是人们出门游玩的好时候.而微软在10月1日凌晨1点召开了发布会,向全球用户揭晓了新一代Windows操作系统Windows 10.包括Windows 10的发布时间相关的消息.此消息瞬间成为阿里在美国上市后的最大的IT新闻,微软的下一代操作系统叫做Windows 10,与之前大家普遍猜测的"Windows TH"."Windows X"."Windows One"