【问题标题】:How can my WCF service recover from unavailable message queue?我的 WCF 服务如何从不可用的消息队列中恢复?
【发布时间】:2018-01-10 05:54:38
【问题描述】:

我有一个从 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(Version& version, Boolean& activeDirectoryEnabled)

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

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

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

System.ServiceModel.Channels.TransportManagerContainer..ctor(TransportChannelListener listener)

【问题讨论】:

  • 所以您的意思是即使您捕获了异常,但仍然存在关联的不良状态?
  • @N-ate:这是我对正在发生的事情的猜测。 (即使我捕捉到异常,中止服务主机并创建一个新的服务主机)
  • 不知道是不是跟executionContext或者synchronizationContext有关。

标签: wcf msmq netmsmqbinding msmq-wcf


【解决方案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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 2014-04-14
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    • 2012-01-26
    相关资源
    最近更新 更多