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

ple*_*ijf 2 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();
}
Run Code Online (Sandbox Code Playgroud)

一旦消息到达,我的目标是显然处理该消息.我的代码目前有一个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;
}
Run Code Online (Sandbox Code Playgroud)

在任何时候,我是否需要呼叫EndPeek()队列?

也许是为了避免内存泄漏,就像这个问题所暗示的那样?我很确定我不必这样做,但文档不是很清楚.如果没有'结束'它,它就不会'开始'某事是100%正确的:)

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

Jer*_*ert 10

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

API描述是否明确表示您必须调用EndPeek()每次调用BeginPeek()?不是我能找到的任何主题,不仅如此,它似乎在这里说明相反的情况:

要使用BeginPeek,请创建一个处理异步操作结果的事件处理程序,并将其与事件委托关联.BeginPeek启动异步查看操作; 的 MessageQueue通知,通过所述的上升PeekCompleted 情况下,当一个消息到达队列.然后,MessageQueue可以通过调用EndPeek(IAsyncResult) 通过使用检索结果来访问消息PeekCompletedEventArgs.

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

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

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

API有其他警告标志,它的实现可能会留下一些需要的东西,最突出的是它不遵循已建立的.Begin...()/ .End...()异步操作模式,但在中间引入了事件处理程序,产生了一个我从未见过的奇怪的混合.然后是让这个Message类继承的非常可疑的决定Component,这给每个实例增加了相当大的开销,并提出了何时以及如何处理它的问题......总而言之,不是微软最好的工作.

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

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