我有一个处理私有本地消息队列(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)
,它同样会将消息提取给我,但它不会从队列中删除该消息。
答案 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()
的代码将停止工作)并且现有的实现已经违反了既定的做法。