我有使用异步API支持从MSMQ读取的代码,即使用BeginReceive(),EndReceive()和ReceivedCompleted事件。基本模式是(取自MessageQueue.ReceiveCompleted Event ...
void StartListening()
{
_msgQ.ReceiveCompleted += ReceiveCompletedEventHandler(FooReceiveCompleted);
_msgQ.BeginReceive();
}
void FooReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
{
Message msg = _msgQ.EndReceive();
// Do stuff with message.
// Set up listening for next message.
_msgQ.BeginReceive();
}
void StopListening()
{
_msgQ.Close();
}
我看到的问题是,总是有一个未决的BeginReceive()等待新消息,并且通过阅读.Net文档,似乎没有官方/推荐的方式来清理此信息以停止监听。
如果我在没有消息接收的情况下调用EndReceive(),则调用将阻塞,直到有消息可用为止。或者,除非将EnableConnectionCache设置为false,否则Close()不会清除MSMQ上的基础句柄(并因此清除挂起的侦听器),否则,将缓存该句柄,并且不会在调用close时对其进行清理。我可以这样做,但理想情况下,我想使用缓存。
我可以看到的唯一其他选择是启用缓存,然后调用静态方法MessageQueue.ClearConnectionCache(),该方法大概是应用程序范围的,因此将影响与我要关闭的队列无关的队列。
附录:
其他选项(来自MessageQueue.Close())...
关闭并不总是将读取和写入句柄释放到队列中,
因为它们可能是共享的。您可以采取以下任何步骤
确保关闭将读取和写入句柄释放到队列:
创建具有独占访问权限的MessageQueue。为此,请致电
MessageQueue(String,Boolean)或MessageQueue(String,Boolean,
布尔值)构造函数,并将sharedModeDenyReceive参数设置为
真正。
创建禁用连接缓存的MessageQueue。为此,
调用MessageQueue(String,Boolean,Boolean)构造函数并设置
将enableConnectionCache参数设置为false。
禁用连接缓存。为此,设置EnableConnectionCache
属性为假。
因此,我对文档的API的第一印象是,除非不使用缓存或您具有对队列的独占访问权限,否则您无法正确终止队列(使用BeginReceive / EndReceive时)。
最佳答案
问题的症结在于,MSDN上MessageQueue.ReceiveCompleted Event的示例C#使用了没有参数的BeginReceive()。这是一个立即返回到调用者的异步调用,但是它会导致执行异常的异步操作,并可能具有很长的使用寿命。
当我们尝试在MessageQueue上调用Close()时,此未完成的异步操作阻止了MessageQueue的正确释放。
一种解决方案是使用BeginReceive(Timeout);即使没有消息,这也将引发ReceiveCompleted事件,这时我们可以测试一个标志以查看是否正在请求关闭,并允许清理正常进行。也就是说,关闭消息队列的外部请求必须等待,例如一个WaitHandle,ReceiveCompleted事件将发出信号。因此,该模式在几秒钟的短时间BeginReceive()超时(最好是1或2秒)时效果最佳。