我有一个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 listener)
答案 0 :(得分:1)
微软已经将WCF的源代码可见,所以现在我们可以确切地知道发生了什么。
坏消息:WCF的实现方式是,如果对ServiceModel.Start()
的初始调用触发排队错误,则无法恢复。
WCF框架包含一个名为MsmqQueue
的内部类。这个班级有一个static constructor。静态构造函数调用GetMsmqInformation,它可以抛出异常。
阅读静态构造函数的C# Programming Guide:
如果静态构造函数抛出异常,则运行时将不会再次调用它,并且该类型将在运行程序的应用程序域的生命周期内保持未初始化状态。
这里有一个编程课程:不要将异常抛出代码放在静态构造函数中!
显而易见的解决方案不在代码之内。当我创建托管服务时,我可以在消息队列服务上添加服务依赖项。但是,我宁愿用代码然后配置修复这个问题。
另一种解决方案是使用非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);
}
}