Practical .NET
Picking Up Queue Messages: Strategy and Tactics
If you're using MSMQ to offload work from your Web site, you have a number of ways to pick up those messages, including processing those messages as soon as they turn up.
MSMQ provides a way of offloading work from your application to be processed at a later date or on another computer. In an earlier column, I showed how to write messages to a Microsoft Message Queue (MSMQ) for later processing. In this column, I'll show the code for reading your messages from the queue and, more important, give you some options on where to put that code.
Reading a Queue
The easiest way to pull a Message object from the queue is to use the MessageQueue object Receive method (you'll need to instantiate the MessageQueue object, passing it the name of the MSMQ queue you want to read to get access to your queue). Once you retrieve the message, you can pull your data from the Message Body property. If you've put an object in the Body property as I recommended in my previous column, you'll need to set the Message Formatter property to whatever formatter you used when your wrote the message. Even after setting the Formatter property, you'll need to cast the resulting object to the right type. This code does all of that:
Dim q As New MessageQueue(".\private$\emails")
Dim emm As EmailMessage
Dim msg As Message
msg = q.Receive()
Dim msgType(0) As Type
msgType(0) = GetType(EmailMessage)
msg.Formatter = New XmlMessageFormatter(msgType)
emm = CType(msg.Body, EmailMessage)
There is one wrinkle: If the Receive method doesn't find a message, the method waits until a message appears. So this code will run until all the messages are processed and then stop, waiting for another message. If you're writing a program that's supposed to process all the messages in a queue and then shut down (as part of your nightly processing, for example), this isn't going to work for you.
To prevent the Receive method from waiting forever for a message, you can pass a TimeSpan object specifying how long the method should wait for a message to turn up. Unfortunately, when the Receive method gives up waiting, it throws an exception. To prevent this exception from bubbling up and terminating your application, you'll need to wrap the method in a Try
Catch block. Of course, the Receive method could throw an exception for some other reason, so you should check to see if the problem really was a timeout error.
The following code causes the Receive method to wait two seconds before timing out:
Try
msg = q.Receive(New TimeSpan(0, 0, 2))
Catch ex As Exception
If ex.GetType Is GetType(MessageQueueException) Then
Exit Sub
Else
Throw ex
End If
End Try
If the Receive method does time out, the code quietly exits. This is because, presumably, all the messages have been processed. If the Receive method throws some other exception, that exception is passed onto the application.
Leveraging a Windows Service
If you want to process messages as they come in, one solution is to create a Windows Service. The Windows Service can process messages as they arrive, occasionally falling behind if messages arrive faster than the service can process them. Hopefully, though, once the rate of messages slows down, the service will (eventually) catch up. Effectively, even though you're using a queue, you get an "almost-real-time" response.
Creating a Windows Service is easy in Visual Studio: Just select File | New | Project to open the Add New Project dialog box. You'll find a Windows Service project template in the Windows section. Once Visual Studio has created your Windows Service project, you can rename the default class (Service1) to some more meaningful name.
You put your code to process a queue in the service class OnStart method. The simplest way to process messages as they come in is just to call the Receive method in an infinite loop. The Receive method will wait for a message to appear, process it, then go back to wait for the next message:
Dim q As New MessageQueue(".\private$\emails")
Dim msg As Message
Do Until bolStop
msg = q.Receive()
'...process message if found...
Loop
The bolStop variable would be declared outside of the service's two methods (OnStart and OnStop). In the OnStop method (which is called by Windows as part of shutting the service down), you just need to set bolStop to False.
The problem is, of course, breaking out of the Receive method to check that bolStop has been set. Here, again, you'll want to pass a TimeSpan object to the Receive method to give the loop a chance to check to see if the bolStop variable has been changed.
Rather than checking each message as soon as it arrives, you could check for messages on a regular basis. To implement this solution, drag a Timer control from the Toolbox onto your Windows Service design surface. Once there, you can put your code inside the Timer's Tick event and set the Timer's Interval property to the number of milliseconds to wait between checking for messages.
The code in Listing 1 will wake up, process all of the messages on the queue and, once there are no more messages, exit.
Listing 1: Using a Timer to Read MSMQ Messages
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim q As New MessageQueue(".\private$\emails")
Dim msg As Message
Dim bolStop As Boolean
Do Until bolStop
Try
msg = q.Receive(New TimeSpan(0, 0, 10))
Catch ex As Exception
If ex.GetType Is GetType(MessageQueueException) Then
bolStop = True
Else
Throw ex
End If
End Try
'...process message if present...
Loop
End Sub
There's at least one more option to consider when processing MSMQ messages: Launching a program every time a message appears to process that message. I'll discuss that option in my next column.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.