0

我有一个从 Microsoft 消息队列 ( netMsmqBinding) 接收消息的 WCF 服务。

如果消息队列不可用,我希望我的服务能够恢复。我的代码应该无法打开服务,但在延迟后重试。

当队列不可用时,我有代码来识别错误:

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.MsmqQueue.GetMsmqInformation(版本和版本,布尔值和 activeDirectoryEnabled)

System.ServiceModel.Channels.Msmq..cctor()

以及外部异常堆栈的顶部条目:

System.ServiceModel.Channels.MsmqChannelListenerBase`1.get_TransportManagerTable()

System.ServiceModel.Channels.TransportManagerContainer..ctor(TransportChannelListener 监听器)

4

1 回答 1

1

Microsoft 已经使 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);
    }
}
于 2018-01-10T23:33:03.277 回答