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

    环境 1. Intellij Idea 2. Scala 插件 3. http://scala-lang.org/ 教程: idea官方教程: https://www.jetbrains.com/he ...

  2. HTML和CSS经典布局5

    如下图: 需求: 1. 如图 2. 可以从body标签开始. 3. 页面内容高度设置高点,把窗口的滚动条显示出来,但是busy indicator不滚动. <!DOCTYPE html> ...

  3. SOA相关资料整理分享

    昨@幸福框架同学问能否推荐SOA一些资,.想想之前看过不少资料文档,就整理分享下,有需要的可以参考下. 文章链接 理解面向服务的体系结构中企业服务总线场景和解决方案,第 1 部分 SOA 和 web ...

  4. sleep和wait区别

    1. sleep和wait都是用来进行线程控制,他们最大本质的区别是: sleep()不释放同步锁,wait()释放同步锁.               sleep(milliseconds)可以用时 ...

  5. Atitit 图像处理 深刻理解梯度原理计算.v1 qc8

    Atitit 图像处理 深刻理解梯度原理计算.v1 qc8 1.1. 图像处理  梯度计算  基本梯度 内部梯度 外部梯度 方向梯度1 2. 图像梯度就是图像边缘吗?2 1.1. 图像处理  梯度计算 ...

  6. 初识Jsp,JavaBean,Servlet以及一个简单mvc模式的登录界面

    1:JSP JSP的基本语法:指令标识page,include,taglib;page指令标识常用的属性包含Language用来定义要使用的脚本语言:contentType定义JSP字符的编码和页面响 ...

  7. Script component 用法

    在SSIS中,可以使用C#编写脚本,这是十分激动人心的事,能够使用C#代码,使得Script Component无所不能. 第一部分:组件简介Script Component 有三种类型:Source ...

  8. 斐讯Fir302b救砖教程

    首先本人是路由器小白,不算是硬件改装高手,昨天收到了微信活动中的斐讯Fir302b,大概当时得奖的有300人,所以最近肯定很大一批朋友手里有这样的一款路由. 上网查了一下,此款路由可以刷基于tomat ...

  9. 分享一个LiteDB做的简单考试系统辅助工具

    凌晨,被安排在公司值班,因为台风“灿鸿”即将登陆,风力太大,办公楼,车间等重要部分需要关注.所以无聊,那就分享一下,今天给朋友临时做的一个小的考试系统辅助工具吧.其实非常小,需求也很简单,但是可以根据 ...

  10. ASP.NET MVC之表单集合数据自动绑定到对象属性(集合)中

    前言 之前没遇到过这个问题,在项目中遇到这个问题时想法挺好,按照流程走下去,结果事与愿违,于是开始探索着解决方案,接下来我们来看看这个问题,早已经明了的童鞋请绕道,此文仅供未遇到的童鞋提供一种解决方案 ...