5
votes

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;
        }
    }
}
3
Just curious, why not expose a WCF service that uses MSMQ as the binding? 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
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

3 Answers

4
votes

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.

1
votes

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.

0
votes
    private static volatile bool _shouldStop = false;

. . .

            _shouldStop = true;
            mq.Close();

. . .

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

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

            mq.BeginReceive();
        }

. . .