5

I don't understand where this is going wrong. Basically, I have a program which receives from a message queue and processes the messages. The program can be stopped at any time, in which case the message loop finished what it is doing before the program exits. I'm trying to accomplish this with the following code:

private MessageQueue q;
private ManualResetEventSlim idle;

public void Start()
{
    idle = new ManualResetEventSlim();
    q.ReceiveCompleted += this.MessageQueue_ReceiveCompleted;    
    q.BeginReceive();
}    

public void Stop()
{ 
    this.q.Dispose();
    this.idle.Wait();    
}

private void MessageQueue_ReceiveCompleted(object sender, 
    ReceiveCompletedEventArgs e)
{
    Message inMsg;
    try
    {
        inMsg = e.Message;
    }
    catch (Exception ex)
    {
        this.idle.Set();
        return;
    }

    // Handle message

    this.q.BeginReceive();
}

As is hopefully apparent, the Stop method disposes of the message queue, and then waits for the idle wait handle to be set (which should occur as an ReceiveCompleted event will be called when disposed, but the e.Message property should except).

However, the message loop just continues! I've disposed the message queue, but it still manages to read from it and the exception handler is not invoked, meaning the idle.Wait line waits forever.

My understanding is that disposing a message queue SHOULD end any pending receives and invoke the event, but the e.Message (or q.EndReceive) should throw an exception. Is this not the case? If not, how else can I safely exit my message loop?

Thanks

UPDATE:

Here is a complete example (assumes the queue exists)

class Program
{
    static MessageQueue mq;
    static ManualResetEventSlim idleWH;

    static void Main(string[] args)
    {
        idleWH = new ManualResetEventSlim();

        Console.WriteLine("Opening...");
        using (mq = new MessageQueue(@".\private$\test"))
        {
            mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(int) });
            mq.ReceiveCompleted += mq_ReceiveCompleted;

            for (int i = 0; i < 10000; ++i)
                mq.Send(i);

            Console.WriteLine("Begin Receive...");
            mq.BeginReceive();

            Console.WriteLine("Press ENTER to exit loop");
            Console.ReadLine();

            Console.WriteLine("Closing...");

            mq.Close();
        }

        Console.WriteLine("Waiting...");
        idleWH.Wait();

        Console.WriteLine("Press ENTER (ex)");
        //Console.ReadLine();
    }

    static void mq_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
    {
        try
        {
            var msg = mq.EndReceive(e.AsyncResult);
            Console.Title = msg.Body.ToString();

            // Receive next message
            mq.BeginReceive();
        }
        catch (Exception ex)
        {
            idleWH.Set();
            return;
        }
    }
}
Barguast
  • 5,926
  • 9
  • 43
  • 73
  • 2
    Just curious, why not expose a [WCF service that uses MSMQ as the binding](http://msdn.microsoft.com/en-us/library/ms789048.aspx)? This will allow you to not have to worry about the management of the queue and the reading of the messages and focus more on your logic. – casperOne Mar 30 '11 at 12:31
  • It is a race condition. mq_ReceiveCompleted is executed at the same time as the Dispose(), so you are allowed to receive the message that is pending at the moment you called BeginReceive(), or possibly any message that arrives right after that, but before the Dispose() is completed. You cannot rely on the order of two simultaneously executing operations. – Frank Hileman Mar 08 '13 at 23:00

3 Answers3

4

Not quite sure how you made this work at all. You must call MessageQueue.EndReceive() in the event. Only that method can throw the exception. Review the MSDN sample code for the ReceiveCompleted event. And don't catch Exception, that just causes undiagnosable failure. Catch the specific exception you get when you dispose the queue, ObjectDisposedException.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Never the less, it does work. The Message property, as I understand it, does the same as q.EndReceive(e.AsyncResult), and indeed if I put in the EndReceive method instead I get the same result - the queue will continue to be consumed AFTER it has been disposed. – Barguast Mar 30 '11 at 14:08
  • That's accurate, the Message property getter indeed calls EndReceive the first time. Scratch that. Grasping at straws, I can't see the MessageQueue constructor. Is the queue actually opened? – Hans Passant Mar 30 '11 at 14:17
  • OK, the example is in the first post. Oddly, if the queue is empty (and the loop to populate some test data isn't there) there the example works as expected - pressing ENTER causes the event handler to except and the program to exit properly. It seems to only occur if ANY messages are received from the queue. – Barguast Mar 30 '11 at 14:58
1

The only way I could get this working was with a transactional queue. Any non-transactional queue appears to be vulnerable to this. Not an answer, but the best advice I can give to anyone finding this.

Barguast
  • 5,926
  • 9
  • 43
  • 73
0
    private static volatile bool _shouldStop = false;

. . .

            _shouldStop = true;
            mq.Close();

. . .

        try
        {
            var msg = mq.EndReceive(e.AsyncResult);

            if ( _shouldStop)
            {
                idleWH.Set();
                return;
            }

            mq.BeginReceive();
        }

. . .