我是否有义务在使用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)
,它同样获取消息,但它不会从队列中删除消息。
对这个问题给出正确的答案需要一些努力,因为简短的答案("否")可能会产生误导。
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()
的代码将停止工作),并且现有的实现已经违反了既定做法。