使用BeginPeek后,我是否有义务致电EndPeek?

时间:2014-11-18 15:07:28

标签: c# msmq peek msmq-transaction

我有一个处理私有本地消息队列(MSMQ)的Windows服务。当它启动时,它会在队列上注册PeekCompleted的事件处理程序,然后调用异步BeginPeek()以等待消息到达。

protected override void OnStart(string[] args)
{
    if (String.IsNullOrWhiteSpace(args[0]))
        return;

    queue = new MessageQueue(args[0]);
    queue.Formatter = new BinaryMessageFormatter();
    queue.PeekCompleted += new PeekCompletedEventHandler(OnPeekCompleted);

    queue.BeginPeek();
}

一旦消息到达,我的目标是显然处理该消息。我的代码目前有一个queue.Receive()方法来获取消息,包含在一个事务中,以便在处理过程中出现错误时将消息放回队列。再次调用BeginPeek()重启循环。

private static void OnPeekCompleted(Object source, PeekCompletedEventArgs asyncResult)
{
    try
    {
        MessageQueue q = (MessageQueue)source;

        using (MessageQueueTransaction trans = new MessageQueueTransaction())
        {
            trans.Begin();

            Message msg = q.Receive(trans);
            ProcessMessage(msg);

            trans.Commit();
         }

         // Restart the asynchronous peek operation.
         q.BeginPeek();
    }
    catch (MessageQueueException qEx)
    {
        // TODO: Walk it off.
    }
    return;
}

我是否需要在队列中呼叫EndPeek()

也许是为了避免内存泄漏,例如this question暗示?我很确定我不必这样做,但文档不是很清楚。如果没有'结束'它,它就不会'开始'某些东西100%正确:)

顺便说一下:我可以将Receive()行替换为Message msg = q.EndPeek(asyncResult.AsyncResult),它同样会将消息提取给我,但它不会从队列中删除该消息。

1 个答案:

答案 0 :(得分:10)

对这个问题给出正确的答案需要付出一些努力,因为简短的回答(“否”)可能会产生误导。

对于EndPeek()的每次调用,API描述是否明确,您必须致电BeginPeek()吗?不是我能找到的任何主题,不仅如此,它似乎表示相反的here

  

要使用BeginPeek,请创建一个处理结果的事件处理程序   异步操作,并将其与您的事件相关联   代表。 BeginPeek启动异步查看操作;该   通过提升MessageQueue通知PeekCompleted   event,当消息到达队列时。那么MessageQueue就可以了   通过检索调用EndPeek(IAsyncResult) 来访问邮件   结果使用PeekCompletedEventArgs

(强调我的。)这似乎表明你可以使用.EndPeek()或直接从事件args获取消息而没有义务调用.EndPeek()

好的,为了让工作正常,您调用.EndPeek()的实施任务也是如此?至少对于.NET 4.0中的System.Messaging实现,答案是否定的。当您调用.BeginPeek()时,将分配异步操作并注册回调以完成。与此操作关联的非托管资源在此回调中被部分清除,然后才调用事件处理程序。 .EndPeek()实际上并没有进行任何清理 - 它只是等待操作完成(如果尚未完成),检查错误并返回消息。所以你确实可以调用.EndPeek()或者只是从事件args访问消息,或者什么也不做 - 它们都可以正常工作。

很差,是的 - 注意我说“部分清理”了。 MessageQueue的实现有一个问题,即它为每个异步操作分配一个ManualResetEvent,但从不处理它,完全由垃圾收集器决定 - 这是.NET开发人员经常被谴责的事情。 ,但当然微软自己的开发人员也不完美。我没有测试this question中描述的OverlappedData泄漏是否仍然相关,也不是从源头上明显看出来的,但它不会让我感到惊讶。

API有其他警告信号表明它的实现可能会有所不足,最突出的是它不遵循已建立的.Begin...() / .End...()模式进行异步操作,但在中间引入了事件处理程序产生一种奇怪的杂交,我从未见过其他地方。然后有一个非常可疑的决定让Message类继承自Component,这给每个实例增加了相当大的开销,并提出了何时以及如何处理它的问题......总而言之,不是微软最好的工作。

现在,这一切是否意味着你没有“被迫”致电.EndPeek()?是的,从某种意义上说,调用它或不调用它在资源清理或正确性方面没有任何功能差异。但是尽管如此,我的建议仍然是无论如何称呼。为什么?因为任何熟悉异步操作模式如何在其他.NET类中工作的人都希望调用存在,而不是把它放在那里看起来像一个可能导致资源泄漏的bug。如果应用程序出现问题,这样的人可能会合理地花费一些无效的努力来查看“问题代码”。鉴于对.EndPeek()的呼叫与其他机器的开销相比可以忽略不计,我认为程序员的节省可能会超过成本。一种可能的替代方法是插入注释,解释为什么你不调用.EndPeek() - 但很可能这仍然需要更多的程序员周期才能掌握而不仅仅是调用它。

理论上,调用它的另一个原因是API的语义可能在将来发生变化,以便调用.EndPeek();在实践中,这种情况不太可能发生,因为微软传统上不愿意做出这样的重大改变(之前并且合理地没有调用.EndPeek()的代码将停止工作)并且现有的实现已经违反了既定的做法。