Export (0) Print
Expand All
Expand Minimize
56 out of 1075 rated this helpful - Rate this topic

Accessing Message Queues

 

Duncan Mackenzie
Microsoft Developer Network

Revised February 2002

Summary: This article describes how to use System.Messaging classes in the Microsoft .NET Framework to read and write to MSMQ queues. (13 printed pages)

Download Bdadotnet_beta2.msi.

Contents

Introduction
Working with Queues
   Creating Queues Programmatically
   Deleting Queues
Sending Messages
   Simple Method
   Complex Method
Receiving Messages
   Setting a Time-out Value
   Using a Specific Formatter
Conclusion

Introduction

To perform a task asynchronously is to execute it without waiting for a result. This type of processing allows you to start a time-consuming process and continue working without having to wait for that process to finish executing.

Asynchronous processing is ideal to use in many situations—especially on the Web, where a user's request could take a long time to process, but you want to provide a response back to the user immediately. By handling the user's request asynchronously, your system can respond regardless of how long that request may actually take to execute.

There are many ways to add asynchronous processing to your Microsoft® .NET application. For example, you could take advantage of the workflow features of Microsoft Exchange 2000 or Microsoft BizTalk™ Server. You could use COM+ Queued Components through COM interop, or you could work directly with Message Queuing (MSMQ). An upcoming Architectural Topics article will compare these alternatives in depth.

MSMQ, which is used by both BizTalk and COM+ Queued Components, is a part of the Microsoft Windows® NT 4.0 Option Pack and the server versions of Microsoft Windows 2000. On Windows NT 4.0 Workstation, Windows 2000 Professional, and Windows XP Professional, you can use MSMQ for accessing local private queues, as is done in this article. Through MSMQ, your application can place data into a queue, where it will be maintained until an application (either a different application, or the same one that placed the data there) retrieves it. By placing a message representing a task (for example, an order to be processed) into a queue rather than actually processing the task synchronously, your main system is only delayed by the length of time required to post to the queue. Any number of different systems can post messages to a queue, and any number can be used to retrieve and process those same messages, providing scalability to your application.

In this article, we will show how you can use the System.Messaging classes provided by the .NET Framework to work with MSMQ and add asynchronous workflow to your application.

As mentioned earlier, MSMQ is available for both Windows NT 4.0, Windows 2000, and Windows XP Professional, but for our examples we will assume that you have a Windows 2000 server running with MSMQ installed. The examples in this article assume this server is the same machine that is running your .NET code. However, in a real system your code may be connecting to message queues located on a separate server.

Working with Queues

You have to be able to specify a message queue before you can use it. To do this, you need a way to describe a queue uniquely and consistently in your applications—and .NET provides three different ways to access a specific queue.

  • Specify a queue by its path

    The first method, is through the queue's path (<servername>\private$\<queuename>, liquidsoap\private$\Orders), which specifies the actual server name (or "." for the local server) and the full path to the queue.

  • Specify a queue by format name

    The second option is the Format Name, which is a string that describes the queue using some connection details and the queue's path (DIRECT=OS:liquidsoap\private$\Orders) or using a special globally unique identifier (GUID) that uniquely identifies the message queue.

  • Specify a queue by label

    Finally, the third method of specifying a queue is by using the queue's label ("My Orders"), which is a value that you can assign through code or through the MSMQ administration interface.

    Using either the label or the path methods causes a bit of overhead, because MSMQ must resolve those descriptions into the Format Name that it uses internally to describe individual queues. Using a Format Name directly avoids the name resolution, making it a more efficient method, and is actually the only way you can refer to a queue if you want your client application to be able to function when a queue is offline.

Note   Offline use of message queues is not covered in this article. See the MSMQ section of the Platform SDK for comprehensive coverage of message queuing.

After specifying the queue by name, you have to create an instance of the System.Messaging.MessageQueue object that will represent the queue. The code to follow, which assumes you have included "Imports System.Messaging" at the top of your code, creates a new instance of the MessageQueue object, using the queue's path to identify it:

Private Const QUEUE_NAME As String = ".\private$\Orders"
Dim msgQ As New MessageQueue(QUEUE_NAME)

Note   See Example 1 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Note the use of "." in the queue's path, which specifies the local machine, instead of using the actual server name. As long as you have a private queue named "Orders" defined in MSMQ on your machine, this code should successfully obtain an object reference to that queue. For a more robust application, you could check if the queue exists and automatically create it if it does not, although performing this type of check is relatively time-consuming.

Creating Queues Programmatically

Although the MSMQ interface is available for creating, deleting, and exploring message queues, you can also work with queues through the System.Messaging namespace. Several static methods (meaning you can call them without creating an actual instance of the class) have been provided on the MessageQueue class that give you the ability to check if a queue exists (Exists), create a queue (Create), and delete a queue (Delete). Using these methods, your application can check for the existence of a queue, and create it automatically if it does not exist. The function, GetQ, listed next provides the exact functionality just described:

Private Function GetQ(ByVal queueName As String) As MessageQueue
    Dim msgQ As MessageQueue
    'Create the queue if it does not already exist
    If Not MessageQueue.Exists(queueName) Then
        Try
            'Create the message queue and the MessageQueue object
            msgQ = MessageQueue.Create(queueName)
        Catch CreateException As Exception
            'Error could occur creating queue if the code does
            'not have sufficient permissions to create queues.
            Throw New Exception("Error Creating Queue", CreateException)
        End Try
    Else
        Try
            msgQ = New MessageQueue(queueName)
        Catch GetException As Exception
            Throw New Exception("Error Getting Queue", GetException)
        End Try
    End If
    Return msgQ
End Function

Note   See GetQ in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

This type of code can simplify deployment because it removes the need to create any queues ahead of time, but the relative expense of calling Exists means that you will achieve better performance if the queue is created by the install, and your program can just assume it exists. It is also worth noting that your receiving code would also have to check for the existence of the desired queue, since it may not exist until after at least one message has been placed into it.

Note   Creating and deleting queues requires certain security permissions, and by default can only be done by the creator/owner of the queue or by an administrator. If you do not have sufficient permissions an exception will be raised with an "Access Denied" message.

Deleting Queues

The remaining static method that you will find useful is Delete, which takes a path to a queue, just like its two companions, Create and Exists, and in this case uses that path to find a queue and remove it from the server. This is, of course, a rather drastic action because it will destroy any messages currently stored inside that queue that could result in the loss of important data. To ensure a queue is successfully deleted, place the call to Delete within an error-handling structure similar to the one shown here:

Try
    ' Delete the specified queue
    MessageQueue.Delete(queuePath)
Catch e As Exception
    ' Throw exception to caller
    Throw New Exception("Deletion Failed", e)
End Try

Note   See Example 2 and DeleteQ in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Sending Messages

The System.Messaging namespace provides both a simple and a complex method for sending messages. The difference between the two methods is not in the messages themselves, but in the degree of control each method provides over the message format and delivery. Using either method, you can send any type of object you wish; the examples in this article will send Strings, DataSets, and even a class that is declared within the example code.

Simple Method

To send a message to a queue using the simple method requires only a few steps: First, obtain a reference to the appropriate message queue (using the queue's path, format name, or label), and then use the MessageQueue object's Send method, supplying the object you wish to send and (if desired) a label for your message. That's it. All the options that control how a message is sent will use their default values, so you don't have to do anything else. The code shown next leverages the GetQ procedure (described earlier in this article) and sends a simple string message to a local private queue:

Private Const QUEUE_NAME As String = ".\private$\Orders"
Dim msqQ As MessageQueue
Dim msgText As String
' Prepare a text message
msgText = String.Format("Sample Message Sent At {0}", DateTime.Now())
Try
    ' Get a MessageQueue object for the queue
    msqQ = GetQ(QUEUE_NAME)
    ' Send the message to the queue
    msqQ.Send(msgText)
Catch e As Exception
    ' Handle the error 
    …
End Try

Note   See Example 3 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

To specify a label for your message, a second parameter can also be included in your call to the Send method:

msgQ.Send(sMessage, "Sample Message")

Using the MSMQ explorer in Windows NT or the Computer Management tools in Windows 2000 and XP, you can view the queues that exist on your machine and inspect the individual messages in each queue. If you assigned a label to your message, that label will appear in the messages view; otherwise, the label area will appear blank.

ms978425.bdadotnetasync1_01(en-us,MSDN.10).gif

Figure 1. MSMQ Window from Windows 2000 Computer Management

To view the contents of an individual message, right-click the message, click Properties, and then click the Body tab. In the case of a simple send, with all the options set to their default, whatever object you sent will have been serialized into XML, similar to the sample shown next (and in Figure 2):

<?xml version="1.0"?>
<string>Sample Message Sent At 5/25/2001 3:51:48 PM</string>

ms978425.bdadotnetasync1_02(en-us,MSDN.10).gif

Figure 2. Message body serialized into XML

Note   XML is the default format for sending messages, but it is possible to choose a different format. Changing the formatting used to serialize a message is covered in the Complex Method section.

Any object can be sent using this method simply by passing that object in to the Send method. Whatever you provide as an argument will be serialized into XML (see the preceding note) and placed into the body of the message. In addition to basic data types, such as String, Integer, and Double, two other types of objects are commonly sent through MSMQ: DataSets and application-specific object classes created by the user. An example of sending both of these objects is provided next, showing how easy it is to send any type of information using the System.Messaging classes.

To send a DataSet, all you really need to do differently is have an instance of a DataSet created and pass it in to the MessageQueue object's Send method. Once again, just as with the String sent earlier, the DataSet will be serialized into XML and placed into the body of the message:

Dim msgQ As MessageQueue
Dim msgText As String
Dim dSet As DataSet
' External code that populates a DataSet object
dSet = GetDataSet()
' Get a MessageQueue object for the queue
msgQ = GetQ(QUEUE_NAME)
' Send message containing DatSet to queue with "Order" label
msgQ.Send(dSet, "Order")
    

Note   See Example 4 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

If your system has its own classes to hold data, such as an Order class, you can send one of those objects through MSMQ, just like the code shown above sent a DataSet. The .NET Messaging classes will take care of serializing your object instance to XML and adding it to the message. In the example to follow, an instance of the Order class is created and its properties populated. The Send method of our MessageQueue object is then used to place this object into a message and add it to the queue. After we discuss the complex method for sending messages, we will cover how to retrieve this and any other type of object when you are dealing with incoming messages from the queue:

Dim msgQ As MessageQueue
Dim myOrder As New Order()
'Populate the order object
myOrder.CustomerID = "ALKI"
myOrder.ID = 34
myOrder.ShipDate = DateTime.Now()
'Get a MessageQueue object for the queue
msgQ = GetQ(QUEUE_NAME)
'Send message containing order to queue
msgQ.Send(myOrder, "Order")

Note   See Example 5 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Complex Method

Although the Send method is suitable for most purposes, more control over the sending process is sometimes necessary. An alternative to using the Send method (as shown in the examples so far in this article) to directly send an object is to work with an instance of the System.Messaging.Message class. By creating a Message object, which represents a single MSMQ message, you can set options for formatting, encryption, time-out and more, giving you access to a wide variety of additional properties beyond those available using the simple Send method.

One of the key message options you may wish to specify controls how the message is serialized for sending. By default, this is set to use XMLMessageFormatter, which serializes objects into an XML representation and is suitable for most purposes. Two other formatters are available, however: the BinaryMessageFormatter and ActiveXMessageFormatter, both of which serialize objects into a binary (not human-readable like XML) format. The Microsoft ActiveX® formatter is used when you are passing primitives or COM objects to a non-.NET system, such as to a Microsoft Visual Basic® 6.0 application, and shouldn't be used unless that functionality is required. The Binary formatter can be faster than the default XML formatter, so it is worth considering if the performance of your MSMQ work is an issue. It is important to consider the trade-off you make by using a binary format instead of XML: In return for increased performance you lose the ability to read and parse the message content directly.

Note   Regardless of which formatter you choose to send the message, you must use the same formatter to receive the message from the queue.

The following code example shows how to use the Complex method for sending a message, setting a few options including formatting and encryption before using the MessageQueue object's Send method with the preconfigured Message object:

Dim msgQ As MessageQueue
'Create a new Message object to send
Dim myMsg As New Message()
'Create new instance of Order class, defined at
'end of this file
Dim myOrder As New Order()
'Populate the order object
myOrder.CustomerID = "ALKI"
myOrder.ID = 34
myOrder.ShipDate = DateTime.Now()

msgQ = GetQ(QUEUE_NAME)

With myMsg
    .Label = "Order"
    .Formatter = New BinaryMessageFormatter()
    .AppSpecific = 34
    .Priority = MessagePriority.VeryHigh
    .Body = myOrder
End With
'Send Message object, which already contains
'instance of Order class inside it
msgQ.Send(myMsg)

Note   See Example 6 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

In this example, because the BinaryMessageFormatter is being used instead of the default XML formatter, the class being sent (Order) needs to support the ISerializable interface or be marked with the <Serializable()> attribute. In the complete code sample, the attribute method is used.

Receiving Messages

Once you have placed messages onto your queue, someone will likely wish to retrieve those messages, taking the data off the queue in the order in which it was added. To obtain a message from the queue, and then retrieve the data it contains, requires a few important steps:

  1. First, as with sending a message, obtain a MessageQueue object pointing at the appropriate queue:
    Dim msgQ As New MessageQueue(QUEUE_NAME)
    
    
  2. Next, use the Receive method of the queue to obtain the first message from the queue:
    Dim myMessage As Message
    myMessage = msgQ.Receive()
    
    
  3. Then, if you are using the XML formatter (the default), set up the formatter of the Message object with a list of possible data types that might be contained in the message. In this case, we are expecting only one possible data type, String:
    Dim targetTypes(0) As Type
    targetTypes(0) = GetType(String)
    myMessage.Formatter = New XmlMessageFormatter(targetTypes)
    
    
  4. Finally, use the Body property to retrieve your data, casting it into the appropriate type:
    Dim msgText As String
    msgText = CStr(myMessage.Body)
    
    
Note   See Example 7 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Setting a Time-out Value

The steps just listed are all that is required to retrieve a message, but as it is now, the preceding code will block, or "hang," on the call to msgQ.Receive until a message is available. If the queue contained no messages, your program could be left waiting at this line for an indefinite period of time. The Receive method allows you to specify a time-out value by supplying a System.TimeSpan object as a parameter. Using one of the several different constructors available, you can create the TimeSpan object right in the call to Receive:

objMessage = objQ.Receive(New TimeSpan(0, 0, 30))

The line of code just shown specifies a 30-second time-out value, using the three-parameter version of the TimeSpan constructor (hours, minutes, seconds), but you could specify any amount of time. If you use this feature, and specify a time-out value when calling Receive, you should wrap this call in an error-handling routine, because if no message can be received before time is up, an error will occur:

Dim myMessage As Message
Try
    myMessage = msgQ.Receive(New TimeSpan(0, 0, 30))
Catch eReceive As MessageQueueException
    Console.WriteLine("{0} ({1})", _
            eReceive.Message, _
            eReceive.MessageQueueErrorCode.ToString)
End Try

Note   See Example 8 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Using a Specific Formatter

The process is the same regardless of the data type that you sent, as long as the XML formatter was used. If another formatter, such as the BinaryMessageFormatter, was used, you must set the message to use the correct formatter before you can retrieve the message body. The following code shows retrieval of an instance of the Order class using the binary formatter:

Dim msgQ As MessageQueue
Dim myOrder As Order
' Get a MessageQueue object for the queue
msgQ = GetQ(QUEUE_NAME)
' Specify the BinaryMessageFormatter
msgQ.Formatter = New BinaryMessageFormatter()
' Read a message off the queue and transform it into an order object
myOrder = CType(msgQ.Receive().Body, Order)
' Do something with the order object
Console.WriteLine(myOrder.CustomerID)

Note   See Example 9 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

The SendMessage procedure uses another variation on sending messages: By setting the MessageQueue object's Formatter property, you can control the serialization used for a simple send.

This entire article is about handling work asynchronously, making sure that nothing else has to wait while a process completes, but the Receive method used in the last few examples is completely synchronous: Your code has to wait while the entire message is pulled down from the message queue. This delay wouldn't be noticeable from any of our examples, but a message can be up to 4 MB in size (not impossible to get up to that size if you are sending DataSets around) and you might not want to wait while all of that is pulled into your program's memory space. With large messages and slow network links in mind, the MessageQueue object provides an asynchronous way to receive messages in addition to the Receive method. To receive a message asynchronously, you proceed as before and obtain a reference to the appropriate queue, and then, instead of calling Receive, you will call BeginReceive (specifying a time-out if desired). This starts the receive process, and execution will continue immediately with the next line of code:

Private Const QUEUE_NAME As String = "liquidsoap\private$\Orders"
Private WithEvents msgQ As MessageQueue
' Get a MessageQueue object for the queue
msgQ = GetQ(QUEUE_NAME)
' Start listening for messages
msgQ.BeginReceive()

When a message has been successfully retrieved, the MessageQueue object's ReceiveCompleted event will fire and your event handler will be called. The retrieved message is sent to your event handler as part of one of its parameters, and you can use that object to retrieve the actual data as usual:

Public Sub msgQ_ReceiveCompleted(ByVal sender As Object, _
        ByVal e As ReceiveCompletedEventArgs) _
        Handles msgQ.ReceiveCompleted
    Dim msgText As String
    Dim eMsg As Message
    'Get message from event arguments (e)
    eMsg = e.Message
    'Set up the formatter with the only expected type (string)
    eMsg.Formatter = _
        New XmlMessageFormatter(New System.Type() {GetType(String)})
    'Grab the body and cast to String
    msgText = CStr(eMsg.Body)
    Console.WriteLine(msgText)
    'Set flag so loop in AsyncReceive can exit
    msgReceived = True
End Sub

Note   See Example 10 in the complete BDAdotNetAsync1.vb sample code (see top of article for download).

Conclusion

One of the primary ways to implement asynchronous processing in your application is through Message Queuing (MSMQ). The System.Messaging classes provide you with the ability to send and read queue messages containing either .NET or COM data types. Other ways to implement asynchronous processing will be compared in an upcoming Architectural Topics article.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.