在微服務與事件驅動架構普及前,Microsoft Message Queuing (MSMQ) 早已提供可靠的非同步訊息傳遞、負載平衡與錯誤重試機制。本文展示如何使用 C# 與 .NET Framework 建立一個生產級的 Windows 服務,支援多佇列並行處理、三種路由策略 (RoundRobin/AppSpecific/Assembly)、效能計數器監控與標準化安裝部署。
┌─────────────────────────────────────────────────────────────┐
│ MSDNMessageService (Windows Service) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ CWorker │ │ CWorker │ │ CWorker │ ... │
│ │ (Queue: test │ │ (Queue: test │ │ (Queue: test │ │
│ │ _load1) │ │ _load2) │ │ _load3) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌──────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐ │
│ │ CWorkerThread│ │ CWorkerThread│ │ CWorkerThread│ │
│ │ (Thread 1) │ │ (Thread 1) │ │ (Thread 1) │ │
│ │ (Thread 2) │ │ │ │ (Thread 2) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
RoundRobin AppSpecific Assembly (IWebMessage)
| 類別 | 繼承/實作 | 核心職責 |
|---|---|---|
ServiceControl | ServiceBase | 服務進入點,解析 XML 設定,建立/管理 CWorker 集合 |
CWorker | - | 對應單一輸入佇列,建立 N 個 CWorkerThread,轉發 Start/Stop/Pause/Continue |
CWorkerThread (abstract) | - | 抽象基類,封裝訊息接收迴圈、錯誤處理、佇列開關、資源釋放鉤子 |
CWorkerThreadRoundRobin | CWorkerThread | 輪詢模式:依序將訊息轉發至多個輸出佇列 |
CWorkerThreadAppSpecific | CWorkerThread | 依訊息 AppSpecific 屬性值決定目標輸出佇列 |
CWorkerThreadAssembly | CWorkerThread | 動態載入組件,呼叫實作 IWebMessage 的 Process() 方法 |
public enum WebMessageReturn { ReturnGood, ReturnBad, ReturnAbort }
public interface IWebMessage
{
WebMessageReturn Process(string label, string body, int appSpecific);
void Release(); // 釋放昂貴資源 (DB連線、檔案控制代碼)
}
<?xml version="1.0"?>
<configuration>
<ProcessList>
<!-- RoundRobin: 2 threads, 輸出輪詢 2 queues -->
<ProcessDefinition
ProcessName="Worker1"
ProcessType="RoundRobin"
ProcessThreads="2"
InputQueue=".\private$\test_load1"
ErrorQueue=".\private$\test_error">
<OutputList>
<OutputDefinition OutputName=".\private$\test_out11" />
<OutputDefinition OutputName=".\private$\test_out12" />
</OutputList>
</ProcessDefinition>
<!-- Assembly: 1 thread, 呼叫自訂組件 -->
<ProcessDefinition
ProcessName="Worker2"
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>
protected void ProcessMessages()
{
while (bRun)
{
if (bPause) { Thread.Sleep(500); continue; }
try
{
// 1. 接收訊息 (1秒逾時)
Message mInput = mqInput.Receive(new TimeSpan(0,0,1));
// 2. 處理訊息 (衍生類實作)
ProcessMessage(mInput);
// 3. 更新效能計數器
pcMsgTotWorker.Increment();
pcMsgSecWorker.Increment();
}
catch (MessageQueueException mqe) when (mqe.ErrorCode == -1072824293)
{
// 逾時忽略,繼續迴圈
}
catch (CWorkerThreadException ex)
{
ProcessError(mInput, ex.Terminate); // 送錯誤佇列,決定是否終止執行緒
}
catch (Exception)
{
ProcessError(mInput, true); // 未知錯誤 → 強制終止
}
}
}
public class CWorkerThreadAssembly : CWorkerThread
{
private IWebMessage _messageProcessor;
private string _assemblyPath, _typeName;
protected override void OnStart()
{
_assemblyPath = wfThread.OutputName[0]; // DLL 路徑
_typeName = wfThread.OutputName[1]; // 完整類別名
Assembly asm = Assembly.LoadFrom(_assemblyPath);
Type type = asm.GetType(_typeName);
object instance = Activator.CreateInstance(type);
_messageProcessor = (IWebMessage)instance;
}
protected override void ProcessMessage(Message msg)
{
WebMessageReturn ret = _messageProcessor.Process(
msg.Label, (string)msg.Body, msg.AppSpecific);
switch (ret)
{
case WebMessageReturn.ReturnBad:
throw new CWorkerThreadException("Message marked bad", false);
case WebMessageReturn.ReturnAbort:
throw new CWorkerThreadException("Process terminating", true);
}
}
protected override void OnPause() => _messageProcessor?.Release();
protected override void OnContinue() => OnStart(); // 重新載入
protected override void OnStop() => _messageProcessor?.Release();
}
CounterCreationData[] counters = new CounterCreationData[2];
counters[0] = new CounterCreationData("Messages/Total", "Total Messages", PerformanceCounterType.NumberOfItems64);
counters[1] = new CounterCreationData("Messages/Second", "Messages Per Sec", PerformanceCounterType.RateOfChangePerSecond32);
PerformanceCounterCategory.Create("MSDN Message Service", "Counters", counters);
pcMsgTotWorker = new PerformanceCounter("MSDN Message Service", "Messages/Total", processName, false);
pcMsgSecWorker = new PerformanceCounter("MSDN Message Service", "Messages/Second", processName, false);
pcMsgTotWorker.RawValue = 0; pcMsgSecWorker.RawValue = 0;
// 處理迴圈中
pcMsgTotWorker.Increment(); pcMsgSecWorker.Increment();
if (!EventLog.SourceExists("MSDNMessageService"))
EventLog.CreateEventSource("MSDNMessageService", "Application");
EventLog.WriteEntry("Service started", EventLogEntryType.Information);
[RunInstaller(true)]
public class ServiceRegister : Installer
{
public ServiceRegister()
{
ServiceInstaller svc = new ServiceInstaller
{
ServiceName = "MSDNMessageService",
DisplayName = "MSDN Message Queue Processor",
StartType = ServiceStartMode.Manual
};
ServiceProcessInstaller proc = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem // 需 MSMQ 權限
};
Installers.AddRange(new Installer[] { svc, proc });
}
}
安裝指令:installutil.exe MSDNMessageService.exe
解除安裝:installutil.exe /u MSDNMessageService.exe
| 原 MSMQ 架構 | 現代替代方案 | 遷移重點 |
|---|---|---|
| MSMQ 佇列 | RabbitMQ / Azure Service Bus / Kafka | 協定轉換、訊息格式標準化 (JSON/Protobuf) |
| Windows Service | K8s Deployment / Azure Container Apps / BackgroundService | 容器化、健康檢查、水平擴展 |
| XML 設定檔 | appsettings.json / Consul / etcd | 動態配置、環境隔離 |
| 效能計數器 | Prometheus + Grafana / Application Insights | 標準化指標、分散式追蹤 |
| ActiveXMessageFormatter | System.Text.Json / Protobuf.NET | 跨平台序列化、版本容忍 |
本文展示的訊息佇列處理服務,雖基於 .NET Framework 1.1/2.0 時代技術,但其「設定驅動、多執行緒佇列消費、策略模式路由、介面導向擴展、完整觀測性」的架構設計原則,放在今天的雲原生環境同樣適用。理解這套經典實作,能幫助開發者在遷移至現代訊息中介軟體時,保留核心業務邏輯並正確映射至新技術棧。