![](/img/trans.png)
[英]How to enforce message queue sequence with multiple WCF service instances
[英]How can my WCF service recover from unavailable message queue?
我有一個WCF服務,該服務從Microsoft消息隊列( netMsmqBinding
)接收消息。
如果消息隊列不可用,我希望我的服務恢復。 我的代碼應該無法打開服務,但是在延遲后再試一次。
當隊列不可用時,我有代碼可以識別錯誤:
static bool ExceptionIsBecauseMsmqNotStarted(TypeInitializationException ex)
{
MsmqException msmqException = ex.InnerException as MsmqException;
return ((msmqException != null) && msmqException.HResult == (unchecked((int)(0xc00e000b))));
}
因此,這應該很簡單:我調用ServiceHost.Open()
,捕獲此異常,等待一兩秒鍾,然后重復執行直到我的Open
調用成功為止。
問題是,如果此異常被拋出一次,它將繼續被拋出。 消息隊列可能已經可用,但是我正在運行的進程處於錯誤狀態,並且我繼續獲取TypeInitializationException
直到我關閉進程並重新啟動它。
有辦法解決這個問題嗎? 我可以讓WCF原諒隊列並真正嘗試再次收聽嗎?
這是我的服務開始代碼:
public async void Start()
{
try
{
_log.Debug("Starting the data warehouse service");
while(!_cancellationTokenSource.IsCancellationRequested)
{
try
{
_serviceHost = new ServiceHost(_dataWarehouseWriter);
_serviceHost.Open();
return;
}
catch (TypeInitializationException ex)
{
_serviceHost.Abort();
if(!ExceptionIsBecauseMsmqNotStarted(ex))
{
throw;
}
}
await Task.Delay(1000, _cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
_log.Error("Failed to start the service host", ex);
}
}
這是堆棧信息。 第一次引發內部異常的堆棧跟蹤是:
以及外部異常堆棧的頂部條目:
在System.ServiceModel.Channels.MsmqChannelListenerBase`1.get_TransportManagerTable()
在System.ServiceModel.Channels.TransportManagerContainer..ctor(TransportChannelListener偵聽器)
微軟已經使WCF的源代碼可見,因此現在我們可以准確地了解正在發生的事情。
壞消息:WCF的實現方式是,如果對ServiceModel.Start()
的初始調用觸發了排隊錯誤,則無法恢復。
WCF框架包括一個稱為MsmqQueue
的內部類。 此類具有靜態構造函數 。 靜態構造函數調用GetMsmqInformation ,這可能引發異常。
閱讀有關靜態構造函數的C#編程指南 :
如果靜態構造函數引發異常,則運行時不會再次調用該異常,並且該類型將在程序運行所在的應用程序域的生存期內保持未初始化狀態。
這里有一個編程課:不要將引發異常的代碼放在靜態構造函數中!
顯而易見的解決方案位於代碼之外。 創建托管服務時,可以在消息隊列服務上添加服務依賴項。 但是,我寧願先使用代碼然后進行配置來解決此問題。
另一個解決方案是使用非WCF代碼手動檢查隊列是否可用。
如果消息隊列服務不可用,則方法System.Messaging.MessageQueue.Exists返回false
。 知道這一點,以下工作:
private const string KNOWN_QUEUE_PATH = @".\Private$\datawarehouse";
private static string GetMessageQueuePath()
{
// We can improve this by extracting the queue path from the configuration file
return KNOWN_QUEUE_PATH;
}
public async void Start()
{
try
{
_log.Debug("Starting the data warehouse service");
string queuePath = GetMessageQueuePath();
while(!_cancellationTokenSource.IsCancellationRequested)
{
if (!(System.Messaging.MessageQueue.Exists(queuePath)))
{
_log.Warn($"Unable to find the queue {queuePath}. Will try again shortly");
await Task.Delay(60000, _cancellationTokenSource.Token);
}
else
{
_serviceHost = new ServiceHost(_dataWarehouseWriter);
_serviceHost.Open();
return;
}
}
}
catch(System.OperationCanceledException)
{
_log.Debug("The service start operation was cancelled");
}
catch (Exception ex)
{
_log.Error("Failed to start the service host", ex);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.