摘要:本文概述一种用于处理若干消息队列的 Windows 服务解决方案,重点介绍 .NET 框架和 C# 应用程序。
下载 CSharpMessageService.exe 示例文件 (86 KB)
目录
简介
.NET 框架应用程序
应用程序结构
服务类
检测设备
安装
总结
参考资料
简介
Microsoft 近期推出了一种用于生成集成应用程序的新平台——Microsoft .NET 框架。.NET 框架允许开发人员使用任何编程语言迅速生成和部署 Web 服务和应用程序。Microsoft Intermediate Language (MSIL) 和实时 (JIT) 编译器使这种不依赖语言的框架得以实现。
与 .NET 框架同时面世的还有一种新的编程语言 C#(读作“C sharp”)。C# 是一种简单、新颖、面向对象和类型安全的编程语言。利用 .NET 框架和 C#(除 Microsoft® Visual Basic® 和 Managed C++ 之外),用户可以编写功能强大的 Microsoft Windows® 和 Web 应用程序及服务。本文提供了这样的一个解决方案,它的重点是 .NET 框架和 C# 而不是编程语言。C# 语言的介绍可以在“ C# 简介和概述(英文)”找到。
近期的文章“MSMQ:可伸缩、高可用性的负载平衡解决方案(英文)”介绍了一种解决方案,用于高可用性消息队列 (MSMQ) 的可伸缩负载平衡解决方案体系结构。此解决方案中涉及了一种将 Windows 服务用作智能消息路由器的开发方案。这样的解决方案以前只有 Microsoft Visual C++® 程序员才能实现,而 .NET 框架的出现改变了这种情况。从下面的解决方案中,您可以看到这一点。
.NET 框架应用程序
这里介绍的解决方案是一种用来处理若干消息队列的 Windows 服务;其中每个队列都是由多个线程进行处理(接收和处理消息)。处理程序使用循环法技术或应用程序特定值(消息 AppSpecific 属性)从目的队列列表中路由消息,并使用消息属性来调用组件方法。(示例进程也属于这种情况。)在后一种情况下,组件的要求是它能够实现给定的接口 IWebMessage。要处理错误,应用程序需要将不能处理的消息发送到错误队列中。
消息应用程序的结构与以前的活动模板库 (ATL) 应用程序相似,它们之间的主要不同在于用于管理服务的代码的封装和 .NET 框架组件的使用。要创建 Windows 服务,.NET 框架用户仅仅需要创建一个从 ServiceBase(来自 System.ServiceControl 程序集)继承的类。这毫不奇怪,因为 .NET 框架是面向对象的。
应用程序结构
应用程序中主要的类是 ServiceControl,它是从 ServiceBase 继承的。因而,它必须实现 OnStart 和 OnStop 方法,以及可选的 OnPause 和 OnContinue 方法。事实上,类是在静态方法 Main 内构造的:
using System;
using System.ServiceProcess;
public class ServiceControl: ServiceBase
{
// 创建服务对象的主入口点
public static void Main()
{
ServiceBase.Run(new ServiceControl());
}
// 定义服务参数的构造对象
public ServiceControl()
{
CanPauseAndContinue = true;
ServiceName = "MSDNMessageService";
AutoLog = false;
}
protected override void OnStart(string[] args) {...}
protected override void OnStop() {...}
protected override void OnPause() {...}
protected override void OnContinue() {...}
}
ServiceControl 类创建一系列 CWorker 对象,即,为需要处理的每个消息队列创建 CWorker 类的一个实例。根据定义中处理队列所需的线程数目,CWorker 类依次创建了一系列的 CWorkerThread 对象。CWorkerThread 类创建的一个处理线程将执行实际的服务工作。
使用 CWorker 和 CWorkerThread 类的主要目的是确认服务控件 Start、Stop、Pause 和 Continue 命令。因为这些进程必须是无阻塞的,命令操作最终将在后台处理线程上执行。
CWorkerThread 是一个抽象类,被 CWorkerThreadAppSpecific 、CWorkerThreadRoundRobin 和 CWorkerThreadAssembly 继承。这些类以不同的方式处理消息。前两个类通过给另一队列发送消息来处理消息(其不同之处在于确定接收队列路径的方式),最后一个类则使用消息属性来调用组件方法。
.NET 框架内部的错误处理是以基类 Exception 为基础的。当系统引发或捕获错误时,这些错误必须是从 Exception 中导出的类。CWorkerThreadException 类就是这样一种实现,它通过附加额外属性(用于定义服务是否应继续运行)来扩展基类。
最后,应用程序包含两种结构。这些值类型定义了辅助进程或线程的运行时参数,以简化 CWorker 和 CWorkerThread 对象的结构。使用值类型结构(而不是引用类型类)能够确保这些运行时参数维护的是数值(而不是引用)。
IWebMessage 接口
CWorkerThread 的实现之一是一个调用组件方法的类。这个名为 CWorkerThreadAssembly 的类使用 IWebMessage 接口来定义服务和组件之间的约定。
与当前版本的 Microsoft Visual Studio® 不同,C# 接口可以在任何语言中显式定义,而不需要创建和编译 IDL 文件。C# IWebMessage 接口的定义如下:
public interface IWebMessage
{
WebMessageReturn Process(string sMessageLabel, string sMessageBody, int iAppSpecific);
void Release();
}
ATL 代码中的 Process 方法是为处理消息而指定的。Process 方法的返回代码定义为枚举类型 WebMessageReturn:
public enum WebMessageReturn
{
ReturnGood,
ReturnBad,
ReturnAbort
}
枚举的定义如下:Good 表示继续处理,Bad 表示将消息写入错误队列,Abort 表示终止处理。Release 方法为服务提供了轻松清除类实例的途径。因为仅在垃圾回收的过程中才调用类实例的析构函数,所以确保所有占用昂贵资源(例如数据库连接)的类都有一个能够在析构之前被调用的方法,用来释放这些资源,这是一种非常好的构思。
名称空间
在这里先简单介绍一下名称空间。名称空间允许在内部和外部表示中将应用程序组织成为逻辑元素。服务内的所有代码都包含在 MSDNMessageService.Service 名称空间内。尽管服务代码包含在若干文件中,但是由于它们包含在同一名称空间中,因此用户不需要引用其他文件。
由于 IWebMessage 接口包含在 MSDNMessageService.Interface 名称空间中,因此使用此接口的线程类具有一个接口名称空间。
服务类
应用程序的目的是监视和处理消息队列,每一队列在收到消息时都执行不同的进程。应用程序是作为 Windows 服务来实现的。
ServiceBase 类
如前所述,服务的基本结构是从 ServiceBase 继承的类。重要的方法包括 OnStart、OnStop、OnPause 和 OnContinue,每一个替代方法都与一个服务控制操作直接对应。OnStart 方法的目的是创建 CWorker 对象,而 CWorker 类又创建 CWorkerThread 对象,然后在该对象中创建执行服务工作的线程。
服务的运行时配置(以及 CWorker 和 CWorkerThread 对象的属性)是在基于 XML 的配置文件中维护的。它的名称与创建的 .exe 文件相同,但带有一个 .cfg 后缀。配置示例如下:
ProcessName="Worker1"
ProcessDesc="Message Worker with 2 Threads"
ProcessType="AppSpecific"
ProcessThreads="2"
InputQueue=".\private$\test_load1"
ErrorQueue=".\private$\test_error">
ProcessName="Worker2"
ProcessDesc="Assembly Worker with 1 Thread"
ProcessType="Assembly"
ProcessThreads="1"
InputQueue=".\private$\test_load2"
ErrorQueue=".\private$\test_error">
对此信息的访问通过来自 System.Configuration 程序集的 ConfigManager 类来管理。静态 Get 方法返回信息的集合,这些集合将被枚举以获得单个属性。这些属性集的设置决定了辅助对象的运行时特征。除了这一配置文件,您还应该创建定义 XML 文件结构的图元文件,并在其中引用位于服务器 machine.cfg 配置文件中的图元文件:
InternalName="Process" PublicName="ProcessList"
PublicRowName="ProcessDefinition"
SchemaGeneratorFlags="EMITXMLSCHEMA">
InternalName="Output" PublicName="OutputList"
PublicRowName="OutputDefinition"
SchemaGeneratorFlags="EMITXMLSCHEMA">
PrimaryTable="Process" PrimaryColumns="ProcessName"
ForeignTable="Output" ForeignColumns="ProcessName"
MetaFlags="USECONTAINMENT"/>
由于 Service 类必须维护一个已创建辅助对象的列表,因此使用了 Hashtable 集合,用于保持类型对象的名称/数值对列表。Hashtable 不仅支持枚举,还允许通过关键字来查询值。在应用程序中,XML 进程名称是唯一的关键字:
private Hashtable htWorkers = new Hashtable();
IConfigCollection cWorkers = ConfigManager.Get("ProcessList", new AppDomainSelector());
foreach (IConfigItem ciWorker in cWorkers)
{
WorkerFormatter sfWorker = new WorkerFormatter();
sfWorker.ProcessName = (string)ciWorker["ProcessName"];
sfWorker.ProcessDesc = (string)ciWorker["ProcessDesc"];
sfWorker.NumberThreads = (int)ciWorker["ProcessThreads"];
sfWorker.InputQueue = (string)ciWorker["InputQueue"];
sfWorker.ErrorQueue = (string)ciWorker["ErrorQueue"];
// 计算并定义进程类型
switch ((int)ciWorker["ProcessType"])
{
case 0:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessRoundRobin;
break;
case 1:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessAppSpecific;
break;
case 2:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessAssembly;
break;
default:
throw new Exception("Unknown Processing Type");
}
// 执行更多的工作以读取输出信息
string sProcessName = (string)ciWorker["ProcessName"];
if (htWorkers.ContainsKey(sProcessName))
throw new ArgumentException("Process Name Must be Unique: " + sProcessName);
htWorkers.Add(sProcessName, new CWorker(sfWorker));
}
在这段代码中没有包含的主要信息是输出数据的获取。每一个进程定义中都有一组相应的输出定义项。该信息是通过如下的简单查询读取的:
string sQuery = "SELECT * FROM OutputList WHERE ProcessName=" +
sfWorker.ProcessName + " AND Selector=appdomain://";
ConfigQuery qQuery = new ConfigQuery(sQuery);
IConfigCollection cOutputs = ConfigManager.Get("OutputList", qQuery);
int iSize = cOutputs.Count, iLoop = 0;
sfWorker.OutputName = new string[iSize];
foreach (IConfigItem ciOutput in cOutputs)
sfWorker.OutputName[iLoop++] = (string)ciOutput["OutputName"];
CWorkerThread 和 Cworker 类都有相应的服务控制方法,根据服务控制操作进行调用。由于 Hashtable 中引用了每一个 CWorker 对象,因此需要枚举 Hashtable 的内容,以调用适当的服务控制方法:
foreach (CWorker cWorker in htWorkers.Values)
cWorker.Start();
类似地,实现的 OnPause、OnContinue 和 OnStop 方法是通过调用 CWorker 对象上的相应方法来执行操作的。
CWorker 类
CWorker 类的主要功能是创建和管理 CWorkerThread 对象。Start、Stop、Pause 和 Continue 方法调用相应的 CWorkerThread 方法。实际的 CWorkerThread 对象是在Start 方法中创建的。与使用 Hashtable 管理辅助对象引用的 Service 类相似,CWorker 使用 ArrayList(简单的动态数组)来维护线程对象的列表。
在这个数组内部,CWorker 类创建了 CWorkerThread 类的一个实现版本。CWorkerThread 类(将在下面讨论)是一个必须继承的抽象类。导出类定义了消息的处理方式:
aThreads = new ArrayList();
for (int idx=0; idx{
WorkerThreadFormatter wfThread = new WorkerThreadFormatter();
wfThread.ProcessName = sfWorker.ProcessName;
wfThread.ProcessDesc = sfWorker.ProcessDesc;
wfThread.ThreadNumber = idx;
wfThread.InputQueue = sfWorker.InputQueue;
wfThread.ErrorQueue = sfWorker.ErrorQueue;
wfThread.OutputName = sfWorker.OutputName;
// 定义辅助类型,并将其插入辅助线程结构
CWorkerThread wtBase;
switch (sfWorker.ProcessType)
{
case WorkerFormatter.SFProcessType.ProcessRoundRobin:
wtBase = new CWorkerThreadRoundRobin(this, wfThread);
break;
case WorkerFormatter.SFProcessType.ProcessAppSpecific:
wtBase = new CWorkerThreadAppSpecific(this, wfThread);
break;
case WorkerFormatter.SFProcessType.ProcessAssembly:
wtBase = new CWorkerThreadAssembly(this, wfThread);
break;
default:
throw new Exception("Unknown Processing Type");
}
// 添加对数组的调用
aThreads.Insert(idx, wtBase);
}
一旦所有的对象都已创建,就可以通过调用每个线程对象的 Start 方法来启动它们:
foreach(CWorkerThread cThread in aThreads)
cThread.Start();
Stop、Pause 和 Continue 方法在 foreach 循环里执行的操作类似。Stop 方法具有如下的垃圾收集操作:
GC.SuppressFinalize(this);
在类析构函数中将调用 Stop 方法,这样,在没有显式调用 Stop 方法的情况下也可以正确地终止对象。如果调用了 Stop 方法,将不需要析构函数。SuppressFinalize 方法能够防止调用对象的 Finalize 方法(析构函数的实际实现)。
CWorkerThread 抽象类
CWorkerThread 是一个由 CWorkerThreadAppSpecifc、CWorkerThreadRoundRobin 和 CWorkerThreadAssembly 继承的抽象类。无论如何处理消息,队列的大部分处理是相同的,所以 CWorkerThread 类提供了这一功能。这个类提供了抽象方法(必须被实际方法替代)以管理资源和处理消息。
类的工作再一次通过 Start、Stop、Pause 和 Continue 方法来实现。在 Start 方法中引用了输入和错误队列。在 .NET 框架中,消息由 System.Messaging 名称空间处理:
// 尝试打开队列,并设置默认的读写属性
MessageQueue mqInput = new MessageQueue(sInputQueue);
mqInput.MessageReadPropertyFilter.Body = true;
mqInput.MessageReadPropertyFilter.AppSpecific = true;
MessageQueue mqError = new MessageQueue(sErrorQueue);
// 如果使用 MSMQ COM,则将格式化程序设置为 ActiveX
mqInput.Formatter = new ActiveXMessageFormatter();
mqError.Formatter = new ActiveXMessageFormatter();
一旦定义了消息队列引用,即会创建一个线程用于实际的处理函数(称为 ProcessMessages)。在 .NET 框架中,使用 System.Threading 名称空间很容易实现线程处理:
procMessage = new Thread(new ThreadStart(ProcessMessages));
procMessage.Start();
ProcessMessages 函数是基于 Boolean 值的处理循环。当数值设为 False,处理循环将终止。因此,线程对象的 Stop 方法只设置这一 Boolean 值,然后关闭打开的消息队列,并加入带有主线程的线程:
// 加入服务线程和处理线程
bRun = false;
procMessage.Join();
// 关闭打开的消息队列
mqInput.Close();
mqError.Close();
Pause 方法只设置一个 Boolean 值,使处理线程休眠半秒钟:
if (bPause)
Thread.Sleep(500);
最后,每一个 Start、Stop、Pause 和 Continue 方法将调用抽象的 OnStart、OnStop、OnPause 和 OnContinue 方法。这些抽象方法为实现的类提供了挂钩,以捕获和释放所需的资源。
ProcessMessages 循环具有如下基本结构:
接收 Message。
如果 Message 具有成功的 Receive,则调用抽象 ProcessMessage 方法。
如果 Receive 或 ProcessMessage 失败,将 Message 发送至错误队列中。
Message mInput;
try
{
// 从队列中读取,并等候 1 秒
mInput = mqInput.Receive(new TimeSpan(0,0,0,1));
}
catch (MessageQueueException mqe)
{
// 将消息设置为 null
mInput = null;
// 查看错误代码,了解是否超时
if (mqe.ErrorCode != (-1072824293) ) //0xC00E001B
{
// 如果未超时,发出一个错误并记录错误号
LogError("Error: " + mqe.Message);
throw mqe;
}
}
if (mInput != null)
{
// 得到一个要处理的消息,调用处理消息抽象方法
try
{
ProcessMessage(mInput);
}
// 捕获已知异常状态的错误
catch (CWorkerThreadException ex)
{
ProcessError(mInput, ex.Terminate);
}
// 捕获未知异常,并调用 Terminate
catch
{
ProcessError(mInput, true);
}
}
ProcessError 方法将错误的消息发送至错误队列。另外,它也可能引发异常来终止线程。如果ProcessMessage 方法引发了终止错误或 CWorkerThreadException 类型,它将执行此操作。
CworkerThread 导出类
任何从 CWorkerThread 中继承的类都必须提供 OnStart、OnStop、OnPause、OnContinue 和 ProcessMessage 方法。OnStart 和 OnStop 方法获取并释放处理资源。OnPause 和 OnContinue 方法允许临时释放和重新获取这些资源。ProcessMessage 方法应该处理消息,并在出现失败事件时引发 CWorkerThreadException 异常。
由于 CWorkerThread 构造函数定义运行时参数,导出类必须调用基类构造函数:
public CWorkerThreadDerived(CWorker v_cParent, WorkerThreadFormatter v_wfThread)
: base (v_cParent, v_wfThread) {}
导出类提供了两种类型的处理:将消息发送至另一队列,或者调用组件方法。接收和发送消息的两种实现使用了循环技术或应用程序偏移(保留在消息 AppSpecific 属性中),作为使用哪一队列的决定因素。此方案中的配置文件应该包括队列路径的列表。实现的 OnStart 和 OnStop 方法应该打开和关闭对这些队列的引用:
iQueues = wfThread.OutputName.Length;
mqOutput = new MessageQueue[iQueues];
for (int idx=0; idx{
mqOutput[idx] = new MessageQueue(wfThread.OutputName[idx]);
mqOutput[idx].Formatter = new ActiveXMessageFormatter();
}
在这些方案中,消息的处理很简单:将消息发送必要的输出队列。在循环情况下,这个进程为:
try
{
mqOutput[iNextQueue].Send(v_mInput);
}
catch (Exception ex)
{
// 如果错误强制终止异常
throw new CWorkerThreadException(ex.Message, true);
}
// 计算下一个队列号
iNextQueue++;
iNextQueue %= iQueues;
后一种调用带消息参数的组件的实现方法比较有趣。ProcessMessage 方法使用 IWebMessage 接口调入一个 .NET 组件。OnStart 和 OnStop 方法获取和释放此组件的引用。
此方案中的配置文件应该包含两个项目:完整的类名和类所在文件的位置。按照 IWebMessage 接口中的定义,在组件上调用 Process 方法。
要获取对象引用,需要使用 Activator.CreateInstance 方法。此函数需要一个程序集类型。在这里,它是从程序集文件路径和类名中导出的。一旦获取对象引用,它将被放入合适的接口:
private IWebMessage iwmSample;
private string sFilePath, sTypeName;
// 保存程序集路径和类型名称
sFilePath = wfThread.OutputName[0];
sTypeName = wfThread.OutputName[1];
// 获取对必要对象的引用
Assembly asmSample = Assembly.LoadFrom(sFilePath);
Type typSample = asmSample.GetType(sTypeName);
object objSample = Activator.CreateInstance(typSample);
// 定义给对象的必要接口
iwmSample = (IWebMessage)objSample;
获取对象引用后,ProcessMessage 方法将在 IWebMessage 接口上调用 Process 方法:
WebMessageReturn wbrSample;
try
{
// 定义方法调用的参数
string sLabel = v_mInput.Label;
string sBody = (string)v_mInput.Body;
int iAppSpecific = v_mInput.AppSpecific;
// 调用方法并捕捉返回代码
wbrSample = iwmSample.Process(sLabel, sBody, iAppSpecific);
}
catch (InvalidCastException ex)
{
// 如果在消息内容中发生错误,则强制发出一个非终止异常
throw new CWorkerThreadException(ex.Message, false);
}
catch (Exception ex)
{
// 如果错误调用程序集,则强制发出终止异常
throw new CWorkerThreadException(ex.Message, true);
}
// 如果没有错误,则检查对象调用的返回状态
switch (wbrSample)
{
case WebMessageReturn.ReturnBad:
throw new CWorkerThreadException
("Unable to process message: Message marked bad", false);
case WebMessageReturn.ReturnAbort:
throw new CWorkerThreadException
("Unable to process message: Process terminating", true);
default:
break;
}
提供的示例组件将消息正文写入数据库表。如果捕获到严重数据库错误,您可能希望终止处理过程,但是在这里,仅仅将消息标记为错误的消息。
由于此示例中创建的类实例可能会获取并保留昂贵的数据库资源,所以用 OnPause 和 OnContinue 方法释放和重新获取对象引用。
检测设备
就象在所有优秀的应用程序中一样,检测设备用于监测应用程序的状态。.NET 框架大大简化了将事件日志、性能计数器和 Windows 管理检测设备 (WMI) 纳入应用程序的过程。消息应用程序使用时间日志和性能计数器,二者都是来自 System.Diagnostics 程序集。
在 ServiceBase 类中,您可以自动启用事件日志。另外,ServiceBase EventLog 成员支持写入应用程序事件日志:
EventLog.WriteEntry(sMyMessage, EventLogEntryType.Information);
对于写入事件日志而不是应用程序日志的应用程序,它能够很容易地创建和获取 EventLog 资源的引用(正如在 CWorker 类中所做的一样),并能够使用 WriteEntry 方法记录日志项:
private EventLog cLog;
string sSource = ServiceControl.ServiceControlName;
string sLog = "Application";
// 查看源是否存在,如果不存在,则创建源
if (!EventLog.SourceExists(sSource))
EventLog.CreateEventSource(sSource, sLog);
// 创建日志对象,并引用现在定义的源
cLog = new EventLog();
cLog.Source = sSource;
// 在日志中写入条目,表明创建成功
cLog.WriteEntry("已成功创建", EventLogEntryType.Information);
.NET 框架大大简化了性能计数器。对于每一个处理线程、线程导出的用户和整个应用程序,这一消息应用程序都能提供计数器,用于跟踪消息数量和每秒钟处理消息的数量。要提供此功能,您需要定义性能计数器的类别,然后增加相应的计数器实例。
性能计数器的类别在服务 OnStart 方法中定义。这些类别代表两种计数器——消息总数和每秒钟处理的消息数:
CounterCreationData[] cdMessage = new CounterCreationData[2];
cdMessage[0] = new CounterCreationData("Messages/Total", "Total Messages Processed",
PerformanceCounterType.NumberOfItems64);
cdMessage[1] = new CounterCreationData("Messages/Second", "Messages Processed a Second",
PerformanceCounterType.RateOfChangePerSecond32);
PerformanceCounterCategory.Create("MSDN Message Service", "MSDN Message Service Counters", cdMessage);
一旦定义了性能计数器类别,将创建 PerformanceCounter 对象以访问计数器实例功能。PerformanceCounter 对象需要类别、计数器名称和一个可选的实例名称。对于辅助进程,将使用来自 XML 文件的进程名称,代码如下:
pcMsgTotWorker = new PerformanceCounter("MSDN Message Service", "Messages/Total", sProcessName);
pcMsgSecWorker = new PerformanceCounter("MSDN Message Service", "Messages/Second", sProcessName);
pcMsgTotWorker.RawValue = 0;
pcMsgSecWorker.RawValue = 0;
要增加计数器的值,仅仅需要调用适当的方法:
pcMsgTotWorker.IncrementBy(1);
pcMsgSecWorker.IncrementBy(1);
最后说明一点,服务终止时,安装的性能计数器类别应该从系统中删除:
PerformanceCounterCategory.Delete("MSDN Message Service");
由于性能计数器在 .NET 框架中工作,因此需要运行一项特殊的服务。此服务 (PerfCounterService) 提供了共享内存。计数器信息将写入共享内存,并被性能计数器系统读取。
安装
在结束以前,我们来简要介绍一下安装以及称为 installutil.exe 的安装工具。由于此应用程序是 Windows 服务,它必须使用 installutil.exe 来安装。因此,需要使用一个从 System.Configuration.Install 程序集中继承的 Installer 类:
public class ServiceRegister: Installer
{
private ServiceInstaller serviceInstaller;
private ServiceProcessInstaller processInstaller;
public ServiceRegister()
{
// 创建服务安装程序
serviceInstaller = new ServiceInstaller();
serviceInstaller.StartType = ServiceStart.Manual;
serviceInstaller.ServiceName = ServiceControl.ServiceControlName;
serviceInstaller.DisplayName = ServiceControl.ServiceControlDesc;
Installers.Add(serviceInstaller);
// 创建进程安装程序
processInstaller = new ServiceProcessInstaller();
processInstaller.RunUnderSystemAccount = true;
Installers.Add(processInstaller);
}
}
如此示例类所示,对于一个 Windows 服务,服务和服务进程各需要一个安装程序,以定义运行服务的帐户。其他安装程序允许注册事件日志和性能计数器等资源。
总结
从这个 .NET 框架应用程序示例中可以看出,以前只有 Visual C++ 程序员能够编写的应用程序,现在使用简单的面向对象程序即可实现。尽管我们的重点是 C#,但本文所述的内容也同样适用于 Visual Basic 和 Managed C++。新的 .NET 框架使开发人员能够使用任何编程语言来创建功能强大、可伸缩的 Windows 应用程序和服务。
新的 .NET 框架不仅简化和扩展了编程的种种可能,还能够轻松地将人们经常遗忘的应用程序检测设备(例如性能监测计数器和事件日志通知)合并到应用程序中。尽管这里的应用程序没有使用 Windows 管理检测设备 (WMI),但 .NET 框架同样也可以应用它。
参考资料
可伸缩的高可用性业务对象结构(英文)
MSMQ:可伸缩的高可用性负载平衡解决方案(英文)
C# 简介和概述(英文)
C# 参考(英文)
MSDN Online .NET 信息(英文)
关于作者
Carl Nolan 在北加利福尼亚的 Microsoft 电子商务解决方案小组的西区工作。该小组的工作重点是使用 Microsoft Windows .NET 平台开发基于 Internet 的解决方案。他的电子邮件地址是 carlnol@microsoft.com。
--------------------------------------------------------------------------------
请以 IE4.0 以上版本 800 * 600 浏览本站
©2001 Microsoft Corporation 版权所有。保留所有权利。使用规定。
下载 CSharpMessageService.exe 示例文件 (86 KB)
目录
简介
.NET 框架应用程序
应用程序结构
服务类
检测设备
安装
总结
参考资料
简介
Microsoft 近期推出了一种用于生成集成应用程序的新平台——Microsoft .NET 框架。.NET 框架允许开发人员使用任何编程语言迅速生成和部署 Web 服务和应用程序。Microsoft Intermediate Language (MSIL) 和实时 (JIT) 编译器使这种不依赖语言的框架得以实现。
与 .NET 框架同时面世的还有一种新的编程语言 C#(读作“C sharp”)。C# 是一种简单、新颖、面向对象和类型安全的编程语言。利用 .NET 框架和 C#(除 Microsoft® Visual Basic® 和 Managed C++ 之外),用户可以编写功能强大的 Microsoft Windows® 和 Web 应用程序及服务。本文提供了这样的一个解决方案,它的重点是 .NET 框架和 C# 而不是编程语言。C# 语言的介绍可以在“ C# 简介和概述(英文)”找到。
近期的文章“MSMQ:可伸缩、高可用性的负载平衡解决方案(英文)”介绍了一种解决方案,用于高可用性消息队列 (MSMQ) 的可伸缩负载平衡解决方案体系结构。此解决方案中涉及了一种将 Windows 服务用作智能消息路由器的开发方案。这样的解决方案以前只有 Microsoft Visual C++® 程序员才能实现,而 .NET 框架的出现改变了这种情况。从下面的解决方案中,您可以看到这一点。
.NET 框架应用程序
这里介绍的解决方案是一种用来处理若干消息队列的 Windows 服务;其中每个队列都是由多个线程进行处理(接收和处理消息)。处理程序使用循环法技术或应用程序特定值(消息 AppSpecific 属性)从目的队列列表中路由消息,并使用消息属性来调用组件方法。(示例进程也属于这种情况。)在后一种情况下,组件的要求是它能够实现给定的接口 IWebMessage。要处理错误,应用程序需要将不能处理的消息发送到错误队列中。
消息应用程序的结构与以前的活动模板库 (ATL) 应用程序相似,它们之间的主要不同在于用于管理服务的代码的封装和 .NET 框架组件的使用。要创建 Windows 服务,.NET 框架用户仅仅需要创建一个从 ServiceBase(来自 System.ServiceControl 程序集)继承的类。这毫不奇怪,因为 .NET 框架是面向对象的。
应用程序结构
应用程序中主要的类是 ServiceControl,它是从 ServiceBase 继承的。因而,它必须实现 OnStart 和 OnStop 方法,以及可选的 OnPause 和 OnContinue 方法。事实上,类是在静态方法 Main 内构造的:
using System;
using System.ServiceProcess;
public class ServiceControl: ServiceBase
{
// 创建服务对象的主入口点
public static void Main()
{
ServiceBase.Run(new ServiceControl());
}
// 定义服务参数的构造对象
public ServiceControl()
{
CanPauseAndContinue = true;
ServiceName = "MSDNMessageService";
AutoLog = false;
}
protected override void OnStart(string[] args) {...}
protected override void OnStop() {...}
protected override void OnPause() {...}
protected override void OnContinue() {...}
}
ServiceControl 类创建一系列 CWorker 对象,即,为需要处理的每个消息队列创建 CWorker 类的一个实例。根据定义中处理队列所需的线程数目,CWorker 类依次创建了一系列的 CWorkerThread 对象。CWorkerThread 类创建的一个处理线程将执行实际的服务工作。
使用 CWorker 和 CWorkerThread 类的主要目的是确认服务控件 Start、Stop、Pause 和 Continue 命令。因为这些进程必须是无阻塞的,命令操作最终将在后台处理线程上执行。
CWorkerThread 是一个抽象类,被 CWorkerThreadAppSpecific 、CWorkerThreadRoundRobin 和 CWorkerThreadAssembly 继承。这些类以不同的方式处理消息。前两个类通过给另一队列发送消息来处理消息(其不同之处在于确定接收队列路径的方式),最后一个类则使用消息属性来调用组件方法。
.NET 框架内部的错误处理是以基类 Exception 为基础的。当系统引发或捕获错误时,这些错误必须是从 Exception 中导出的类。CWorkerThreadException 类就是这样一种实现,它通过附加额外属性(用于定义服务是否应继续运行)来扩展基类。
最后,应用程序包含两种结构。这些值类型定义了辅助进程或线程的运行时参数,以简化 CWorker 和 CWorkerThread 对象的结构。使用值类型结构(而不是引用类型类)能够确保这些运行时参数维护的是数值(而不是引用)。
IWebMessage 接口
CWorkerThread 的实现之一是一个调用组件方法的类。这个名为 CWorkerThreadAssembly 的类使用 IWebMessage 接口来定义服务和组件之间的约定。
与当前版本的 Microsoft Visual Studio® 不同,C# 接口可以在任何语言中显式定义,而不需要创建和编译 IDL 文件。C# IWebMessage 接口的定义如下:
public interface IWebMessage
{
WebMessageReturn Process(string sMessageLabel, string sMessageBody, int iAppSpecific);
void Release();
}
ATL 代码中的 Process 方法是为处理消息而指定的。Process 方法的返回代码定义为枚举类型 WebMessageReturn:
public enum WebMessageReturn
{
ReturnGood,
ReturnBad,
ReturnAbort
}
枚举的定义如下:Good 表示继续处理,Bad 表示将消息写入错误队列,Abort 表示终止处理。Release 方法为服务提供了轻松清除类实例的途径。因为仅在垃圾回收的过程中才调用类实例的析构函数,所以确保所有占用昂贵资源(例如数据库连接)的类都有一个能够在析构之前被调用的方法,用来释放这些资源,这是一种非常好的构思。
名称空间
在这里先简单介绍一下名称空间。名称空间允许在内部和外部表示中将应用程序组织成为逻辑元素。服务内的所有代码都包含在 MSDNMessageService.Service 名称空间内。尽管服务代码包含在若干文件中,但是由于它们包含在同一名称空间中,因此用户不需要引用其他文件。
由于 IWebMessage 接口包含在 MSDNMessageService.Interface 名称空间中,因此使用此接口的线程类具有一个接口名称空间。
服务类
应用程序的目的是监视和处理消息队列,每一队列在收到消息时都执行不同的进程。应用程序是作为 Windows 服务来实现的。
ServiceBase 类
如前所述,服务的基本结构是从 ServiceBase 继承的类。重要的方法包括 OnStart、OnStop、OnPause 和 OnContinue,每一个替代方法都与一个服务控制操作直接对应。OnStart 方法的目的是创建 CWorker 对象,而 CWorker 类又创建 CWorkerThread 对象,然后在该对象中创建执行服务工作的线程。
服务的运行时配置(以及 CWorker 和 CWorkerThread 对象的属性)是在基于 XML 的配置文件中维护的。它的名称与创建的 .exe 文件相同,但带有一个 .cfg 后缀。配置示例如下:
ProcessDesc="Message Worker with 2 Threads"
ProcessType="AppSpecific"
ProcessThreads="2"
InputQueue=".\private$\test_load1"
ErrorQueue=".\private$\test_error">
ProcessDesc="Assembly Worker with 1 Thread"
ProcessType="Assembly"
ProcessThreads="1"
InputQueue=".\private$\test_load2"
ErrorQueue=".\private$\test_error">
对此信息的访问通过来自 System.Configuration 程序集的 ConfigManager 类来管理。静态 Get 方法返回信息的集合,这些集合将被枚举以获得单个属性。这些属性集的设置决定了辅助对象的运行时特征。除了这一配置文件,您还应该创建定义 XML 文件结构的图元文件,并在其中引用位于服务器 machine.cfg 配置文件中的图元文件:
PublicRowName="ProcessDefinition"
SchemaGeneratorFlags="EMITXMLSCHEMA">
PublicRowName="OutputDefinition"
SchemaGeneratorFlags="EMITXMLSCHEMA">
ForeignTable="Output" ForeignColumns="ProcessName"
MetaFlags="USECONTAINMENT"/>
由于 Service 类必须维护一个已创建辅助对象的列表,因此使用了 Hashtable 集合,用于保持类型对象的名称/数值对列表。Hashtable 不仅支持枚举,还允许通过关键字来查询值。在应用程序中,XML 进程名称是唯一的关键字:
private Hashtable htWorkers = new Hashtable();
IConfigCollection cWorkers = ConfigManager.Get("ProcessList", new AppDomainSelector());
foreach (IConfigItem ciWorker in cWorkers)
{
WorkerFormatter sfWorker = new WorkerFormatter();
sfWorker.ProcessName = (string)ciWorker["ProcessName"];
sfWorker.ProcessDesc = (string)ciWorker["ProcessDesc"];
sfWorker.NumberThreads = (int)ciWorker["ProcessThreads"];
sfWorker.InputQueue = (string)ciWorker["InputQueue"];
sfWorker.ErrorQueue = (string)ciWorker["ErrorQueue"];
// 计算并定义进程类型
switch ((int)ciWorker["ProcessType"])
{
case 0:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessRoundRobin;
break;
case 1:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessAppSpecific;
break;
case 2:
sfWorker.ProcessType = WorkerFormatter.SFProcessType.ProcessAssembly;
break;
default:
throw new Exception("Unknown Processing Type");
}
// 执行更多的工作以读取输出信息
string sProcessName = (string)ciWorker["ProcessName"];
if (htWorkers.ContainsKey(sProcessName))
throw new ArgumentException("Process Name Must be Unique: " + sProcessName);
htWorkers.Add(sProcessName, new CWorker(sfWorker));
}
在这段代码中没有包含的主要信息是输出数据的获取。每一个进程定义中都有一组相应的输出定义项。该信息是通过如下的简单查询读取的:
string sQuery = "SELECT * FROM OutputList WHERE ProcessName=" +
sfWorker.ProcessName + " AND Selector=appdomain://";
ConfigQuery qQuery = new ConfigQuery(sQuery);
IConfigCollection cOutputs = ConfigManager.Get("OutputList", qQuery);
int iSize = cOutputs.Count, iLoop = 0;
sfWorker.OutputName = new string[iSize];
foreach (IConfigItem ciOutput in cOutputs)
sfWorker.OutputName[iLoop++] = (string)ciOutput["OutputName"];
CWorkerThread 和 Cworker 类都有相应的服务控制方法,根据服务控制操作进行调用。由于 Hashtable 中引用了每一个 CWorker 对象,因此需要枚举 Hashtable 的内容,以调用适当的服务控制方法:
foreach (CWorker cWorker in htWorkers.Values)
cWorker.Start();
类似地,实现的 OnPause、OnContinue 和 OnStop 方法是通过调用 CWorker 对象上的相应方法来执行操作的。
CWorker 类
CWorker 类的主要功能是创建和管理 CWorkerThread 对象。Start、Stop、Pause 和 Continue 方法调用相应的 CWorkerThread 方法。实际的 CWorkerThread 对象是在Start 方法中创建的。与使用 Hashtable 管理辅助对象引用的 Service 类相似,CWorker 使用 ArrayList(简单的动态数组)来维护线程对象的列表。
在这个数组内部,CWorker 类创建了 CWorkerThread 类的一个实现版本。CWorkerThread 类(将在下面讨论)是一个必须继承的抽象类。导出类定义了消息的处理方式:
aThreads = new ArrayList();
for (int idx=0; idx
WorkerThreadFormatter wfThread = new WorkerThreadFormatter();
wfThread.ProcessName = sfWorker.ProcessName;
wfThread.ProcessDesc = sfWorker.ProcessDesc;
wfThread.ThreadNumber = idx;
wfThread.InputQueue = sfWorker.InputQueue;
wfThread.ErrorQueue = sfWorker.ErrorQueue;
wfThread.OutputName = sfWorker.OutputName;
// 定义辅助类型,并将其插入辅助线程结构
CWorkerThread wtBase;
switch (sfWorker.ProcessType)
{
case WorkerFormatter.SFProcessType.ProcessRoundRobin:
wtBase = new CWorkerThreadRoundRobin(this, wfThread);
break;
case WorkerFormatter.SFProcessType.ProcessAppSpecific:
wtBase = new CWorkerThreadAppSpecific(this, wfThread);
break;
case WorkerFormatter.SFProcessType.ProcessAssembly:
wtBase = new CWorkerThreadAssembly(this, wfThread);
break;
default:
throw new Exception("Unknown Processing Type");
}
// 添加对数组的调用
aThreads.Insert(idx, wtBase);
}
一旦所有的对象都已创建,就可以通过调用每个线程对象的 Start 方法来启动它们:
foreach(CWorkerThread cThread in aThreads)
cThread.Start();
Stop、Pause 和 Continue 方法在 foreach 循环里执行的操作类似。Stop 方法具有如下的垃圾收集操作:
GC.SuppressFinalize(this);
在类析构函数中将调用 Stop 方法,这样,在没有显式调用 Stop 方法的情况下也可以正确地终止对象。如果调用了 Stop 方法,将不需要析构函数。SuppressFinalize 方法能够防止调用对象的 Finalize 方法(析构函数的实际实现)。
CWorkerThread 抽象类
CWorkerThread 是一个由 CWorkerThreadAppSpecifc、CWorkerThreadRoundRobin 和 CWorkerThreadAssembly 继承的抽象类。无论如何处理消息,队列的大部分处理是相同的,所以 CWorkerThread 类提供了这一功能。这个类提供了抽象方法(必须被实际方法替代)以管理资源和处理消息。
类的工作再一次通过 Start、Stop、Pause 和 Continue 方法来实现。在 Start 方法中引用了输入和错误队列。在 .NET 框架中,消息由 System.Messaging 名称空间处理:
// 尝试打开队列,并设置默认的读写属性
MessageQueue mqInput = new MessageQueue(sInputQueue);
mqInput.MessageReadPropertyFilter.Body = true;
mqInput.MessageReadPropertyFilter.AppSpecific = true;
MessageQueue mqError = new MessageQueue(sErrorQueue);
// 如果使用 MSMQ COM,则将格式化程序设置为 ActiveX
mqInput.Formatter = new ActiveXMessageFormatter();
mqError.Formatter = new ActiveXMessageFormatter();
一旦定义了消息队列引用,即会创建一个线程用于实际的处理函数(称为 ProcessMessages)。在 .NET 框架中,使用 System.Threading 名称空间很容易实现线程处理:
procMessage = new Thread(new ThreadStart(ProcessMessages));
procMessage.Start();
ProcessMessages 函数是基于 Boolean 值的处理循环。当数值设为 False,处理循环将终止。因此,线程对象的 Stop 方法只设置这一 Boolean 值,然后关闭打开的消息队列,并加入带有主线程的线程:
// 加入服务线程和处理线程
bRun = false;
procMessage.Join();
// 关闭打开的消息队列
mqInput.Close();
mqError.Close();
Pause 方法只设置一个 Boolean 值,使处理线程休眠半秒钟:
if (bPause)
Thread.Sleep(500);
最后,每一个 Start、Stop、Pause 和 Continue 方法将调用抽象的 OnStart、OnStop、OnPause 和 OnContinue 方法。这些抽象方法为实现的类提供了挂钩,以捕获和释放所需的资源。
ProcessMessages 循环具有如下基本结构:
接收 Message。
如果 Message 具有成功的 Receive,则调用抽象 ProcessMessage 方法。
如果 Receive 或 ProcessMessage 失败,将 Message 发送至错误队列中。
Message mInput;
try
{
// 从队列中读取,并等候 1 秒
mInput = mqInput.Receive(new TimeSpan(0,0,0,1));
}
catch (MessageQueueException mqe)
{
// 将消息设置为 null
mInput = null;
// 查看错误代码,了解是否超时
if (mqe.ErrorCode != (-1072824293) ) //0xC00E001B
{
// 如果未超时,发出一个错误并记录错误号
LogError("Error: " + mqe.Message);
throw mqe;
}
}
if (mInput != null)
{
// 得到一个要处理的消息,调用处理消息抽象方法
try
{
ProcessMessage(mInput);
}
// 捕获已知异常状态的错误
catch (CWorkerThreadException ex)
{
ProcessError(mInput, ex.Terminate);
}
// 捕获未知异常,并调用 Terminate
catch
{
ProcessError(mInput, true);
}
}
ProcessError 方法将错误的消息发送至错误队列。另外,它也可能引发异常来终止线程。如果ProcessMessage 方法引发了终止错误或 CWorkerThreadException 类型,它将执行此操作。
CworkerThread 导出类
任何从 CWorkerThread 中继承的类都必须提供 OnStart、OnStop、OnPause、OnContinue 和 ProcessMessage 方法。OnStart 和 OnStop 方法获取并释放处理资源。OnPause 和 OnContinue 方法允许临时释放和重新获取这些资源。ProcessMessage 方法应该处理消息,并在出现失败事件时引发 CWorkerThreadException 异常。
由于 CWorkerThread 构造函数定义运行时参数,导出类必须调用基类构造函数:
public CWorkerThreadDerived(CWorker v_cParent, WorkerThreadFormatter v_wfThread)
: base (v_cParent, v_wfThread) {}
导出类提供了两种类型的处理:将消息发送至另一队列,或者调用组件方法。接收和发送消息的两种实现使用了循环技术或应用程序偏移(保留在消息 AppSpecific 属性中),作为使用哪一队列的决定因素。此方案中的配置文件应该包括队列路径的列表。实现的 OnStart 和 OnStop 方法应该打开和关闭对这些队列的引用:
iQueues = wfThread.OutputName.Length;
mqOutput = new MessageQueue[iQueues];
for (int idx=0; idx
mqOutput[idx] = new MessageQueue(wfThread.OutputName[idx]);
mqOutput[idx].Formatter = new ActiveXMessageFormatter();
}
在这些方案中,消息的处理很简单:将消息发送必要的输出队列。在循环情况下,这个进程为:
try
{
mqOutput[iNextQueue].Send(v_mInput);
}
catch (Exception ex)
{
// 如果错误强制终止异常
throw new CWorkerThreadException(ex.Message, true);
}
// 计算下一个队列号
iNextQueue++;
iNextQueue %= iQueues;
后一种调用带消息参数的组件的实现方法比较有趣。ProcessMessage 方法使用 IWebMessage 接口调入一个 .NET 组件。OnStart 和 OnStop 方法获取和释放此组件的引用。
此方案中的配置文件应该包含两个项目:完整的类名和类所在文件的位置。按照 IWebMessage 接口中的定义,在组件上调用 Process 方法。
要获取对象引用,需要使用 Activator.CreateInstance 方法。此函数需要一个程序集类型。在这里,它是从程序集文件路径和类名中导出的。一旦获取对象引用,它将被放入合适的接口:
private IWebMessage iwmSample;
private string sFilePath, sTypeName;
// 保存程序集路径和类型名称
sFilePath = wfThread.OutputName[0];
sTypeName = wfThread.OutputName[1];
// 获取对必要对象的引用
Assembly asmSample = Assembly.LoadFrom(sFilePath);
Type typSample = asmSample.GetType(sTypeName);
object objSample = Activator.CreateInstance(typSample);
// 定义给对象的必要接口
iwmSample = (IWebMessage)objSample;
获取对象引用后,ProcessMessage 方法将在 IWebMessage 接口上调用 Process 方法:
WebMessageReturn wbrSample;
try
{
// 定义方法调用的参数
string sLabel = v_mInput.Label;
string sBody = (string)v_mInput.Body;
int iAppSpecific = v_mInput.AppSpecific;
// 调用方法并捕捉返回代码
wbrSample = iwmSample.Process(sLabel, sBody, iAppSpecific);
}
catch (InvalidCastException ex)
{
// 如果在消息内容中发生错误,则强制发出一个非终止异常
throw new CWorkerThreadException(ex.Message, false);
}
catch (Exception ex)
{
// 如果错误调用程序集,则强制发出终止异常
throw new CWorkerThreadException(ex.Message, true);
}
// 如果没有错误,则检查对象调用的返回状态
switch (wbrSample)
{
case WebMessageReturn.ReturnBad:
throw new CWorkerThreadException
("Unable to process message: Message marked bad", false);
case WebMessageReturn.ReturnAbort:
throw new CWorkerThreadException
("Unable to process message: Process terminating", true);
default:
break;
}
提供的示例组件将消息正文写入数据库表。如果捕获到严重数据库错误,您可能希望终止处理过程,但是在这里,仅仅将消息标记为错误的消息。
由于此示例中创建的类实例可能会获取并保留昂贵的数据库资源,所以用 OnPause 和 OnContinue 方法释放和重新获取对象引用。
检测设备
就象在所有优秀的应用程序中一样,检测设备用于监测应用程序的状态。.NET 框架大大简化了将事件日志、性能计数器和 Windows 管理检测设备 (WMI) 纳入应用程序的过程。消息应用程序使用时间日志和性能计数器,二者都是来自 System.Diagnostics 程序集。
在 ServiceBase 类中,您可以自动启用事件日志。另外,ServiceBase EventLog 成员支持写入应用程序事件日志:
EventLog.WriteEntry(sMyMessage, EventLogEntryType.Information);
对于写入事件日志而不是应用程序日志的应用程序,它能够很容易地创建和获取 EventLog 资源的引用(正如在 CWorker 类中所做的一样),并能够使用 WriteEntry 方法记录日志项:
private EventLog cLog;
string sSource = ServiceControl.ServiceControlName;
string sLog = "Application";
// 查看源是否存在,如果不存在,则创建源
if (!EventLog.SourceExists(sSource))
EventLog.CreateEventSource(sSource, sLog);
// 创建日志对象,并引用现在定义的源
cLog = new EventLog();
cLog.Source = sSource;
// 在日志中写入条目,表明创建成功
cLog.WriteEntry("已成功创建", EventLogEntryType.Information);
.NET 框架大大简化了性能计数器。对于每一个处理线程、线程导出的用户和整个应用程序,这一消息应用程序都能提供计数器,用于跟踪消息数量和每秒钟处理消息的数量。要提供此功能,您需要定义性能计数器的类别,然后增加相应的计数器实例。
性能计数器的类别在服务 OnStart 方法中定义。这些类别代表两种计数器——消息总数和每秒钟处理的消息数:
CounterCreationData[] cdMessage = new CounterCreationData[2];
cdMessage[0] = new CounterCreationData("Messages/Total", "Total Messages Processed",
PerformanceCounterType.NumberOfItems64);
cdMessage[1] = new CounterCreationData("Messages/Second", "Messages Processed a Second",
PerformanceCounterType.RateOfChangePerSecond32);
PerformanceCounterCategory.Create("MSDN Message Service", "MSDN Message Service Counters", cdMessage);
一旦定义了性能计数器类别,将创建 PerformanceCounter 对象以访问计数器实例功能。PerformanceCounter 对象需要类别、计数器名称和一个可选的实例名称。对于辅助进程,将使用来自 XML 文件的进程名称,代码如下:
pcMsgTotWorker = new PerformanceCounter("MSDN Message Service", "Messages/Total", sProcessName);
pcMsgSecWorker = new PerformanceCounter("MSDN Message Service", "Messages/Second", sProcessName);
pcMsgTotWorker.RawValue = 0;
pcMsgSecWorker.RawValue = 0;
要增加计数器的值,仅仅需要调用适当的方法:
pcMsgTotWorker.IncrementBy(1);
pcMsgSecWorker.IncrementBy(1);
最后说明一点,服务终止时,安装的性能计数器类别应该从系统中删除:
PerformanceCounterCategory.Delete("MSDN Message Service");
由于性能计数器在 .NET 框架中工作,因此需要运行一项特殊的服务。此服务 (PerfCounterService) 提供了共享内存。计数器信息将写入共享内存,并被性能计数器系统读取。
安装
在结束以前,我们来简要介绍一下安装以及称为 installutil.exe 的安装工具。由于此应用程序是 Windows 服务,它必须使用 installutil.exe 来安装。因此,需要使用一个从 System.Configuration.Install 程序集中继承的 Installer 类:
public class ServiceRegister: Installer
{
private ServiceInstaller serviceInstaller;
private ServiceProcessInstaller processInstaller;
public ServiceRegister()
{
// 创建服务安装程序
serviceInstaller = new ServiceInstaller();
serviceInstaller.StartType = ServiceStart.Manual;
serviceInstaller.ServiceName = ServiceControl.ServiceControlName;
serviceInstaller.DisplayName = ServiceControl.ServiceControlDesc;
Installers.Add(serviceInstaller);
// 创建进程安装程序
processInstaller = new ServiceProcessInstaller();
processInstaller.RunUnderSystemAccount = true;
Installers.Add(processInstaller);
}
}
如此示例类所示,对于一个 Windows 服务,服务和服务进程各需要一个安装程序,以定义运行服务的帐户。其他安装程序允许注册事件日志和性能计数器等资源。
总结
从这个 .NET 框架应用程序示例中可以看出,以前只有 Visual C++ 程序员能够编写的应用程序,现在使用简单的面向对象程序即可实现。尽管我们的重点是 C#,但本文所述的内容也同样适用于 Visual Basic 和 Managed C++。新的 .NET 框架使开发人员能够使用任何编程语言来创建功能强大、可伸缩的 Windows 应用程序和服务。
新的 .NET 框架不仅简化和扩展了编程的种种可能,还能够轻松地将人们经常遗忘的应用程序检测设备(例如性能监测计数器和事件日志通知)合并到应用程序中。尽管这里的应用程序没有使用 Windows 管理检测设备 (WMI),但 .NET 框架同样也可以应用它。
参考资料
可伸缩的高可用性业务对象结构(英文)
MSMQ:可伸缩的高可用性负载平衡解决方案(英文)
C# 简介和概述(英文)
C# 参考(英文)
MSDN Online .NET 信息(英文)
关于作者
Carl Nolan 在北加利福尼亚的 Microsoft 电子商务解决方案小组的西区工作。该小组的工作重点是使用 Microsoft Windows .NET 平台开发基于 Internet 的解决方案。他的电子邮件地址是 carlnol@microsoft.com。
--------------------------------------------------------------------------------
请以 IE4.0 以上版本 800 * 600 浏览本站
©2001 Microsoft Corporation 版权所有。保留所有权利。使用规定。
关键字词: