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 principal in PH&V Information Services, specializing in Web development with expertise in SOA, client-side development, and user interface design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His most recent book ("rtfm*") is on writing effective user manuals, and his blog on language and technical writing can be found at rtfmphvis.blogspot.com.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.