首页 > 开发 > 综合 > 正文

C#:消息队列应用程序

2024-07-21 02:19:55
字体:
来源:转载
供稿:网友
摘要:本文概述一种用于处理若干消息队列的 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 后缀。配置示例如下:

<?xml version="1.0"?>
<configuration>
<processlist>
<processdefinition
processname="worker1"
processdesc="message worker with 2 threads"
processtype="appspecific"
processthreads="2"
inputqueue="./private$/test_load1"
errorqueue="./private$/test_error">
<outputlist>
<outputdefinition outputname="./private$/test_out11" />
<outputdefinition outputname="./private$/test_out12" />
</outputlist>
</processdefinition>
<processdefinition
processname="worker2"
processdesc="assembly worker with 1 thread"
processtype="assembly"
processthreads="1"
inputqueue="./private$/test_load2"
errorqueue="./private$/test_error">
<outputlist>
<outputdefinition outputname="c:/msdnmessageservice/messageexample.dll" />
<outputdefinition outputname="msdnmessageservice.messagesample.exampleclass"/>
</outputlist>
</processdefinition>
</processlist>
</configuration>

对此信息的访问通过来自 system.configuration 程序集的 configmanager 类来管理。静态 get 方法返回信息的集合,这些集合将被枚举以获得单个属性。这些属性集的设置决定了辅助对象的运行时特征。除了这一配置文件,您还应该创建定义 xml 文件结构的图元文件,并在其中引用位于服务器 machine.cfg 配置文件中的图元文件:

<?xml version ="1.0"?>
<metadata xmlns="x-schema:catmeta.xms">
<databasemeta internalname="messageservice">
<serverwiring interceptor="core_xmlinterceptor"/>
<collection
internalname="process" publicname="processlist"
publicrowname="processdefinition"
schemageneratorflags="emitxmlschema">
<property internalname="processname" type="string" metaflags="primarykey" />
<property internalname="processdesc" type="string" />
<property internalname="processtype" type="int32" defaultvalue="roundrobin" >
<enum internalname="roundrobin" value="0"/>
<enum internalname="appspecific" value="1"/>
<enum internalname="assembly" value="2"/>
</property>
<property internalname="processthreads" type="int32" defaultvalue="1" />
<property internalname="inputqueue" type="string" />
<property internalname="errorqueue" type="string" />
<property internalname="outputname" type="string" />
<querymeta internalname="all" metaflags="all" />
<querymeta internalname="querybyfile" cellname="__file" operator="equal" />
</collection>
<collection
internalname="output" publicname="outputlist"
publicrowname="outputdefinition"
schemageneratorflags="emitxmlschema">
<property internalname="processname" type="string" metaflags="primarykey" />
<property internalname="outputname" type="string" metaflags="primarykey" />
<querymeta internalname="all" metaflags="all" />
<querymeta internalname="querybyfile" cellname="__file" operator="equal" />
</collection>
</databasemeta>
<relationmeta
primarytable="process" primarycolumns="processname"
foreigntable="output" foreigncolumns="processname"
metaflags="usecontainment"/>
</metadata>

由于 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<sfworker.numberthreads; 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<iqueues; 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 的解决方案。他的电子邮件地址是 [email protected]。









--------------------------------------------------------------------------------
请以 ie4.0 以上版本 800 * 600 浏览本站
&copy;2001 microsoft corporation 版权所有。保留所有权利。使用规定。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表