3
votes

I have code which is reading from MSMQ using the asynchronous API support, that is using BeginReceive(), EndReceive() and the ReceivedCompleted event. The basic pattern is (taken from 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();
}

The issue I can see is that there is always a pending BeginReceive() waiting for a new message, and through reading the .Net docs there does not appear to be an official/recommended way of cleaning this up in order to stop listening.

If I call EndReceive() without there being a message to receive then the call blocks until a message becomes available. Alternatively it appears that Close() will not cleanup the underlying handle on the MSMQ (and therefore the pending listener) unless the EnableConnectionCache is set to false, otherwise the handles are cached and are not cleaned up up on a call to close. I can do this but ideally I would like to use the caching.

The only other option I could see was to enable caching and then call the static method MessageQueue.ClearConnectionCache() which presumably is application domain wide and therefore will affect queues not related to the queue I am trying to close.

ADDENDUM: Additional options (from MessageQueue.Close())...

Close does not always free the read and write handles to a queue, because they might be shared. You can take any of the following steps to ensure that Close frees the read and write handles to a queue:

Create the MessageQueue with exclusive access. To do so, call the MessageQueue(String, Boolean) or MessageQueue(String, Boolean, Boolean) constructor, and set the sharedModeDenyReceive parameter to true.

Create the MessageQueue with connection caching disabled. To do so, call the MessageQueue(String, Boolean, Boolean) constructor and set the enableConnectionCache parameter to false.

Disable connection caching. To do so, set the EnableConnectionCache property to false.

Thus my first impression of the API from the docs is that you cannot terminate a queue properly (when using BeginReceive/EndReceive) unless caching is not used or you have exclusive access to a queue.

2

2 Answers

0
votes

The crux of the problem is that the example C# for MessageQueue.ReceiveCompleted Event on MSDN uses BeginReceive() with no parameters. This is an asynchronous call that immediately returns to the caller, however it results in an outstanding asynchronous operation that can potentially have a very long lifetime.

When we try to call Close() on a MessageQueue this outstanding async operation prevents proper release of the MessageQueue.

One solution is to use BeginReceive(Timeout); this will cause the ReceiveCompleted event to fire even when there is no message, at which point we can test a flag to see is a close is being requested and allow the clean-up to progress normally. That is, the external request to close the message queue must wait on, e.g. a WaitHandle, that the ReceiveCompleted event will signal. Therefore the pattern works best with a short BeginReceive() timeout of a few seconds (ideally 1 or 2 seconds).

0
votes

Have a look here at Event Driven consumer. As of 2018-12-19 this URL returns 404.