Carl Nolan
Microsoft Corporation程序員
摘要:本文概述一種用於處理若干消息隊列的 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 框架的出現改變了這種狀況。從下面的解決方案中,您能夠看到這一點。app
.NET 框架應用程序
這裏介紹的解決方案是一種用來處理若干消息隊列的 Windows 服務;其中每一個隊列都是由多個線程進行處理(接收和處理消息)。處理程序使用循環法技術或應用程序特定值(消息 AppSpecific 屬性)從目的隊列列表中路由消息,並使用消息屬性來調用組件方法。(示例進程也屬於這種狀況。)在後一種狀況下,組件的要求是它可以實現給定的接口 IWebMessage。要處理錯誤,應用程序須要將不能處理的消息發送到錯誤隊列中。框架
消息應用程序的結構與之前的活動模板庫 (ATL) 應用程序類似,它們之間的主要不一樣在於用於管理服務的代碼的封裝和 .NET 框架組件的使用。要建立 Windows 服務,.NET 框架用戶僅僅須要建立一個從 ServiceBase(來自 System.ServiceControl 程序集)繼承的類。這絕不奇怪,由於 .NET 框架是面向對象的。dom
應用程序結構
應用程序中主要的類是 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 的解決方案。他的電子郵件地址是 carlnol@microsoft.com。
--------------------------------------------------------------------------------
請以 IE4.0 以上版本 800 * 600 瀏覽本站
©2001 Microsoft Corporation 版權全部。保留全部權利。使用規定。