Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Strange error: MSMQ listener on separate thread

405 views
Skip to first unread message

David C. de Vogel

unread,
Mar 5, 2003, 3:30:58 PM3/5/03
to
Hi All,

I've written an MSMQ listener class (C# on XP) that raises an event
when a message arrives on the queue specified in the constructor. My
code looks something like this:

static void Main(string[] args)
MessageListener listener = new
MessageListener(".\\private$\\queuename");
listener.NewMessage += new NewMessageEventHandler(NewMessage);
listener.Start();
...

static void NewMessage(object sender, NewMessageEventArgs e)
{
//code to handle the newly arrived message ...
}

The Start() method of my listener begins an asynchronous receive. When
a message arrives it's put in the NewMessageEventArgs, an event is
raised and a new asynch receive is started.

public void Start()
{
mQueue = new MessageQueue(mQueuePath);
mQueue.Formatter ...
mQueue.ReceiveCompleted += new
ReceiveCompletedEventHandler(MesssageReceiveCompleted);
mQueue.BeginReceive();
}

Pretty straightforward I think. Everything's fine when I run the code
in Main() above. My problem arises when I try to start my listener on
a separate thread and the queue in question is empty. So if, instead
of:

listener.Start()

I use:

Thread thread = new Thread(new ThreadStart(listener.Start));
thread.Start();

then the ReceiveCompletedEventHandler in my listener is immediately
fired in the first pass through Start() even though there is no
message on the queue. Obviously, that raises an exception. I handle
the error and find that MessageQueueErrorCode = -1073741536
(C0000120). I can find no documentation on that error code. There is
also an InteropServices.ExternalException with an ErrorCode of
-2147467259 (80004005). Neither of the exceptions have any descriptive
messages.

If there is a message already on the queue and I use the ThreadStart
delegate eveything is fine. Also, once I'm past the first "misfiring"
of the event things are also fine (ie. it only happens once). So, is
my design flawed or is this an issue others have experienced? Thanks
for any advice.

David

Yoel Arnon [MSFT]

unread,
Mar 6, 2003, 6:15:38 AM3/6/03
to
0xc0000120 is "STATUS_CANCELLED". This is what your asynchronous receive
handler (ReceiveCompletedEventHandler in your case) would get when the queue
handler is closed by another thread.

Since you did not post the full code of your application, I could not follow
the exact scheduling of events when the queue is empty. However, I would
expect to see the queue handle closed or go out of context before
ReceiveCompletedEventHandler is called.
Note that in the .NET framework context, if a queue handle goes out of
context and your program did not explicitly closed it, GC (garbage
collection) will do that. Since GC happens in the background, it is hard to
predict when the queue will be closed and STATUS_CANCELLED will be sent to
the handler.

If this explanation does not help you finding the problem, please post a
code sample that I can compile and run and I will try to identify the exact
problem for you (although it may take a while).

HTH,
Yoel
---
This posting is provided "AS IS" with no warranties, and confers no rights.

"David C. de Vogel" <ddev...@yahoo.com> wrote in message
news:38a59bc3.03030...@posting.google.com...

David C. de Vogel

unread,
Mar 6, 2003, 2:54:18 PM3/6/03
to
Hi Yoel,

Thanks for your quick response. It sounds like I'm missing something
obvious but I just don't see it. I understand why the event handler
would throw the exception I just don't know why I would be in the
handler with no message on the queue. I've attached my code below.

David

//Main.cs
namespace MessageListener
{
class MQListen
{
[STAThread]


static void Main(string[] args)
{
MessageListener listener = new MessageListener
(".\\private$\\queuename");

// Wires the AlarmRang method to the Alarm event.
listener.NewMessage += new NewMessageEventHandler (NewMessage);



Thread thread = new Thread(new ThreadStart(listener.Start));
thread.Start();

Console.ReadLine();
}

static void NewMessage(object sender, NewMessageEventArgs e)
{

Console.WriteLine(e.NewMessage.Body);
}
}
}


// MessageListener.cs
using System;
using System.Messaging;

namespace MessageListener
{
public delegate void NewMessageEventHandler(object sender,
NewMessageEventArgs e);

public class MessageListener
{
private string mQueuePath = null;
private MessageQueue mQueue = null;

public event NewMessageEventHandler NewMessage;

public MessageListener(string queue)
{
mQueuePath = queue;
}

public void Start()
{
// Open the queue
mQueue = new MessageQueue(mQueuePath);
//Set its formatter to read string messages
mQueue.Formatter = new XmlMessageFormatter(new Type[]
{typeof(String)});

// Add an event handler for the ReceiveCompleted event.
mQueue.ReceiveCompleted += new
ReceiveCompletedEventHandler(MessageReceiveCompleted);

// Begin the asynchronous receive operation.
mQueue.BeginReceive();
}

public void Stop()
{
// Close the queue and dispose of the resources
mQueue.Close();
mQueue.Dispose();
}

protected virtual void OnNewMessage(NewMessageEventArgs e)
{
if (NewMessage != null)
{
//Invokes the delegate.
NewMessage(this, e);
}
}

private void MessageReceiveCompleted(Object source,
ReceiveCompletedEventArgs asyncResult)
{
MessageQueue queue = null;
try
{
// Connect to the queue.
queue = (MessageQueue)source;

// End the asynchronous receive operation.
Message message = queue.EndReceive(asyncResult.AsyncResult);

// Include the retrieved message in the EventArgs
NewMessageEventArgs e = new NewMessageEventArgs(message);

// Raise the event
OnNewMessage(e);
}
// Handle MessageQueueExceptions
catch(MessageQueueException MQException)
{
//For some reason when the Start() method of this class is
called via
//a ThreadStart delegate like:
// Thread thread = new Thread(new
ThreadStart(listener.Start));
//then this event handler is called automatically. If there
are no
//messages on the source queue then this exception is raised.
Here
//we are ignoring that specific error
if ((int)MQException.MessageQueueErrorCode != -1073741536)
{
Console.WriteLine(MQException.Source);
Console.WriteLine(MQException.Message);
}
}
// Handle other exceptions.
catch(Exception e)
{
}
// Restart the asynchronous receive operation.
queue.BeginReceive();

return;
}
}
}


// NewMessageEventArgs.cs
using System;
using System.Messaging;

namespace MessageListener
{
public class NewMessageEventArgs: EventArgs
{
private readonly Message mNewMessage = null;

public NewMessageEventArgs (Message message)
{
mNewMessage = message;
}

public Message NewMessage
{
get {return mNewMessage;}
}
}
}

"Yoel Arnon [MSFT]" <yo...@online.microsoft.com> wrote in message news:<#Z0T8G94...@TK2MSFTNGP09.phx.gbl>...

Yoel Arnon [MSFT]

unread,
Mar 10, 2003, 8:10:27 AM3/10/03
to
I checked your code and I saw the behavior you described. As it turned out,
it implies from MSMQ design. Here is why:

MSMQ uses thread-allocated objects to store the asynchronous receive
request. These objects belong to the tread that calls BeginReceive.

When these objects go out of context (in other words, when the calling
thread terminates) the async receive fails and the handler gets
STATUS_CANCEL.

The sequence of events when the queue is empty is, then:
- Main thread starts thread #1 (listener.Start)
- Thread #1 calls BeginReceive() and initializes the async receive thread
(let's call it ARThread)
- Async receive objects are allocated in the context of thread #1
- Thread #1 terminates - Async receive objects go out of context
- MessageReceiveCompleted() is called with STATUS_CANCELED

This explains why you got STATUS_CANCEL when the queue was empty.

Why did it work, then, when the queue was not empty?

When your application calls BeginReceive for the first time, MSMQ creates a
thread that handles all the ReceiveCompletedEventHandler calls from this
moment onwards. This additional thread is kept alive until your application
terminates. Therefore, the BeginReceive called in your
MessageReceiveCompleted function is called in the context of that thread.

Therefore, the sequence of events is as follows:
-Main thread starts thread #1 (listener.Start)
- Thread #1 calls BeginReceive() and initializes ARThread
- Async receive objects are allocated in the context of thread #1
- Since there is a message in the queue, ARThread immediately call
MessageReceiveCompleted() to handle the first message and free the async
receive objects
- MessageReceiveCompleted() calls BeginReceive()
- Async receive objects are allocated in the context of ARThread
- Thread #1 terminates, but it has no async receive objects associated any
longer
- From this moment and on, everything works fine because ARThread stays
alive

Note that you still have a chance to get STATUS_CANCEL even if the queue
contains messages, because there is a race between thread #1 termination and
ARThread calling MessageReceiveCompleted. However, my experience (and yours,
as I understand) shows that in practice ARThread wins. You can try playing
with thread #1's priority and generate STATUS_CANCEL when there are messages
in the queue.

Kostas Antonopoulos

unread,
Mar 13, 2003, 9:49:29 AM3/13/03
to
I have a very similar issue to this one (only I am using peek instead
of receive, and I get the PeekCompleted event being raised even though
there are no messages in the queue).

I am thinking of calling either Thread.Suspend() or creating a
ManualResetEvent and calling the WaitOne() method on that, straight
after the BeginPeek(). I have found that both keep the thread from
exiting which causes the PeekCompleted event from being raised, but
I'm not sure which of the two is better and if there are any other
issues with this approach. Any suggestions?

thanks

Kostas

0 new messages