我是否有义务在使用BeginPeek后调用EndPeek

本文关键字:BeginPeek 调用 EndPeek 是否 有义务 | 更新日期: 2023-09-27 18:30:55

我有一个处理私有本地消息队列(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()

也许是为了避免内存泄漏,就像这个问题所暗示的那样?我很确定我不必这样做,但文档对此不是很清楚。只是感觉"开始"某事而不"结束"它就不是 100% 正确的:)

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

我是否有义务在使用BeginPeek后调用EndPeek

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

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

要使用 BeginPeek ,请创建一个处理结果的事件处理程序 的异步操作,并将其与事件关联 委托。 BeginPeek启动异步速览操作;这 通过提高PeekCompleted通知MessageQueue 事件,当消息到达队列时。然后MessageQueue可以 通过调用EndPeek(IAsyncResult)检索来访问消息 使用PeekCompletedEventArgs的结果。

(强调我的。这似乎在说您可以使用.EndPeek(),也可以直接从事件参数中获取消息,而没有义务调用.EndPeek()

好的,那么,您所说的实施任务是否.EndPeek()以使事情正常工作?至少对于 .NET 4.0 中的System.Messaging实现,答案是否定的。调用 .BeginPeek() 时,将分配一个异步操作,并注册一个回调以完成。与此操作关联的非托管资源在此回调中被部分清理,然后才调用事件处理程序。 .EndPeek()实际上并不执行任何清理 - 如果尚未完成,它只是等待操作完成,检查错误并返回消息。因此,确实可以调用.EndPeek()或仅从事件参数访问消息,或者什么都不做 - 它都将同样糟糕地工作。

糟糕的是,是的 - 请注意,我说的是"部分清理"。MessageQueue 的实现存在一个问题,因为它为每个异步操作分配一个ManualResetEvent,但从不释放它,这完全由垃圾回收器决定 - .NET 开发人员经常因为这样做而受到谴责,但当然Microsoft自己的开发人员也不是完美的。我还没有测试这个问题中描述的OverlappedData泄漏是否仍然相关,也不是从源头上立即明显看出,但这不会让我感到惊讶。

该 API 还有其他警告信号,表明它的实现可能会留下一些不足之处,最突出的是它不遵循异步操作的既定.Begin...()/.End...()模式,而是在中间引入了事件处理程序,产生了一种奇怪的混合体我从未在其他任何地方看到过。然后是一个非常可疑的决定,即让Message类从Component继承,这为每个实例增加了相当大的开销,并提出了何时以及如何处置它的问题......总而言之,这不是Microsoft最好的作品。

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

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