.NET
has a built-in mechanism for invoking a
method call on an object: using a delegate asynchronously. The client
creates a delegate class that wraps the method it wants to invoke
synchronously, and the compiler provides definition and
implementation for a BeginInvoke( )
method, which asynchronously calls the required method on the object.
The compiler also generates the EndInvoke( )
method to allow the client to poll for the method completion.
Additionally, .NET provides a helper class called
AsyncCallback
to manage asynchronous callbacks from the
object once the call is done.
Compared with COM+ queued components, the .NET approach leaves much to be desired. First, .NET does not support disconnected work. Both the client and the server have to be running at the same time, and their machines must be connected to each other on the network. Second, the client’s code in the asynchronous case is very different from the usual synchronous invocation of the same method on the object’s interface. Third, there is no built-in support for transactional forwarding of calls to the server, nor is there an auto-retry mechanism. In short, you should use COM+ queued components if you want to invoke asynchronous method calls in .NET.
The
ApplicationQueuing
assembly attribute is used to
configure queuing support for the hosting COM+ application. The
ApplicationQueuing
attribute has two public
properties that you can set. The Boolean
Enabled
property corresponds to the Queued checkbox on the
application’s queuing tab. When set to true
,
it instructs COM+ to create a public message queue, named as the
application, for the use of any queued components in the assembly.
The second public property of ApplicationQueuing
is the Boolean
QueueListenerEnabled
property. It corresponds to the Listen
checkbox on the application’s queuing tab. When set to
true
, it instructs COM+ to activate a listener for
the application when the application is launched. For example, here
is how you enable queued component support for your application and
enable a listener:
//Must be a server application to use queued components [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationQueuing(Enabled = true,QueueListenerEnabled = true)]
The ApplicationQueuing
attribute has an overloaded
default constructor that sets the Enabled
attribute to true
and the
QueueListenerEnabled
attribute to
false
. As a result, the following two statements
are equivalent:
[assembly: ApplicationQueuing] [assembly: ApplicationQueuing(Enabled = true,QueueListenerEnabled = false)]
In
addition to enabling queued
component support at the application level, you must mark your
interfaces as capable of receiving queued calls. You do that by using
the
InterfaceQueuing
attribute.
InterfaceQueuing
has one public Boolean property
called Enabled
that corresponds to the Queued
checkbox on the interface’s Queuing tab.
[InterfaceQueuing(Enabled = true)] public interface IMyInterface { void MyMethod( ); }
The InterfaceQueuing
attribute has an overloaded
default constructor that sets the Enabled
property
to true
and a constructor that accepts a Boolean
parameter. As a result, the following three statements are
equivalent:
[InterfaceQueuing] [InterfaceQueuing(true)] [InterfaceQueuing(Enabled = true)]
Note that your interface must adhere to the queued components design
guidelines discussed in Chapter 8, such as no
out
or ref
parameters. If you
configure your interface as a queued interface using the
InterfaceQueuing
attribute and the interface is
incompatible with queuing requirements, the registration process
fails.
The client
of a queued component cannot create
the queued component directly. It must create a recorder for its
calls using the queue
moniker. A C++ or a Visual
Basic 6.0 program uses the CoGetObject( )
or
GetObject( )
calls. A .NET managed client can use
the static method BindToMoniker( )
of the
Marshal
class, defined as:
public static object BindToMoniker(string monikerName);
BindToMoniker( )
accepts a
moniker string as a parameter and returns
the corresponding object. The Marshal
class is
defined in the
System.Runtime.InteropServices
namespace.
The BindToMoniker( )
method of the
Marshal
class makes writing managed clients for a
queued component as easy as if it were a COM client:
using System.Runtime.InteropServices;//for the Marshal class IMyInterface obj; obj =(IMyInterface)Marshal.BindToMoniker("queue:/new:MyNamespace.MyComponent"); obj.MyMethod( );//call is recorded
In the case of a COM client, the recorder records the calls the
client makes. The recorder only dispatches them to the queued
component queue (more precisely, to its application’s queue)
when the client releases the recorder. A managed client does not use
reference counting, and the recorded calls are dispatched to the
queued component queue when the managed wrapper around the recorder
is garbage collected. The client can expedite dispatching the calls
by explicitly forcing the managed wrapper around the recorder to
release it, using the static
DisposeObject( )
method of the ServicedComponent
class,
passing in the recorder object:
using System.Runtime.InteropServices;//for the Marshal class IMyInterface obj; obj =(IMyInterface)Marshal.BindToMoniker("queue:/new:MyNamespace.MyComponent"); obj.MyMethod( );//call is recorded //Expedite dispatching the recorded calls by disposing of the recorder ServicedComponent sc = obj as ServicedComponent; If(sc !=null) ServicedComponent.DisposeObject(sc);
You can use the
IDisposable
interface instead of calling
DisposeObject()
.
Due to the nature of an asynchronous queued call, managing a failure on both the client’s side (failing to dispatch the calls) and the server’s side (repeatedly failing to execute the call—a poison message) requires a special design approach. As discussed in Chapter 8, both the clients and server can use a queued component exception class to handle the error. You can also provide your product administrator with an administration utility for moving messages between the retry queues.
You
can designate a managed class
as the exception class for your queued component using the
ExceptionClass
attribute. Example 10-15 demonstrates using the
ExceptionClass
attribute.
Example 10-15. Using the ExceptionClass attribute to designate an error-handling class for your queued component
using COMSVCSLib;
public class MyQCException : IPlaybackControl,IMyInterface
{
public void FinalClientRetry( ) {...}
public void FinalServerRetry( ) {...}
public void MyMethod( ){...}
}
[ExceptionClass
("MyQCException")]
public class MyComponent :ServicedComponent,IMyInterface
{...}
In Example 10-15, when you register the assembly
containing MyComponent
with COM+, on the
component’s Advanced tab, the Queuing exception class field
will contain the name of its exception class—in this case,
MyQCException
, as shown in Figure 10-3.
Figure 10-3. After registering the component in Example 10-15 with COM+, its Advanced tab contains the exception class
You
need to know a few more things
about designating a managed class as a queued component’s
exception class. First, it has nothing to do with .NET error handling
via exceptions. The word exception is
overloaded. As far as .NET is concerned, a queued component’s
exception class is not a .NET exception class. Second, the queued
component exception class has to adhere to the requirements of a
queued component exception class described in Chapter 8. These requirements include implementing the
same set of queued interfaces as the queued component itself and
implementing the
IPlaybackControl
interface. To add
IPlaybackControl
to your class definition you need
to add a reference in your project to the COM+ Services type library.
IPlaybackControl
is defined in the
COMSVCSLib
namespace.
As explained in Chapter 8, COM+ provides you with
the IMessageMover
interface, and a standard
implementation of it, for
moving all the messages from one retry
queue to another. Managed clients can access this implementation by
importing the COM+ Services type library and using the
MessageMover
class, defined in the
COMSVCSLib
namespace. Example 10-16
implements the same use-case as Example 8-2.
Example 10-16. MessageMover is used to move messages from the last retry queue to the application’s queue
using COMSVCSLib; IMessageMover messageMover; int moved;//How many messages were moved messageMover = (IMessageMover) new MessageMover( ); //Move all the messages from the last retry queue to the application's queue messageMover.SourcePath = @".PRIVATE$MyApp_4"; messageMover.DestPath = @".PUBLIC$MyApp"; moved = messageMover.MoveMessages( );