MSMQ is a WCF transactional resource manager. When you
create a queue (either programmatically or administratively), you can
create the queue as a transactional queue. If the queue is
transactional, it is durable, and messages always persist to disk. More
importantly, posting messages to and removing messages from the queue
will always be done under a transaction. If the code that tries to
interact with the queue has an ambient transaction, the queue will
silently join that transaction. If no ambient transaction is present,
MSMQ will start a new transaction for that interaction. It is as if the queue is encased
in a TransactionScope
constructed
with Transaction
Scope
Option.
Required
. Once in a transaction, the
queue will commit or roll back along with the accessing transaction. For
example, if the accessing transaction posts a message to the queue and
then aborts, the queue will reject the message.
When a nontransactional client calls a queued service, client-side failures after the call will not roll back posting the message to the queue, and the queued call will be dispatched to the service. However, a client calling a queued service may call under a transaction, as shown in Figure 9-3.
The client calls are converted to WCF messages and then packaged in an MSMQ message (or messages). If the client’s transaction commits, these MSMQ messages are posted to the queue and persist there. If the client’s transaction aborts, the queue discards these MSMQ messages. In effect, WCF provides clients of a queued service with an auto-cancellation mechanism for their asynchronous, potentially disconnected calls. Normal connected asynchronous calls cannot be combined easily, if at all, with transactions, because once the call is dispatched there is no way to recall it in case the original transaction aborts. Unlike connected asynchronous calls, queued service calls are designed for this very transactional scenario. In addition, the client may interact with multiple queued services in the same transaction. Aborting the client transaction for whatever reason will automatically cancel all calls to those queued services.
Since the client may not be on the same machine as the service, and since the client, the service, or both could be disconnected, MSMQ maintains a client-side queue as well. The client-side queue serves as a “proxy” to the service-side queue. In the case of a remote queued call, the client first posts the message to the client-side queue. When (or if) the client is connected, MSMQ will deliver the queued messages from the client-side queue to the service-side queue, as shown in Figure 9-4.
Since MSMQ is a resource manager, removing the message from the client-side queue will create a transaction (if indeed the queue is transactional). If MSMQ fails to deliver the message to the service-side queue for whatever reason (such as a network fault or service machine crash), the delivery transaction will abort, the message removal from the client-side queue will be rolled back, and the message posting to the service-side queue will also be canceled, resulting in the message being back in the client-side queue. At this point, MSMQ will try again to deliver the message. Thus, while you can configure and control failure handling (as you will see later), excluding fatal errors that can never be resolved, queued services actually enjoy a guaranteed delivery mechanism; if it is technically possible to deliver the message (within the confines of the failure-handling modes), the message will get from the client to the service. In effect, this is WCF’s way of providing reliable messaging for queued services. Of course, there is no direct support for the reliable messaging protocol, as there is with connected calls; this is just the analogous mechanism.
When WCF removes a message from the queue for playback to the service, this kick-starts a new transaction (assuming the queue is transactional), as shown in Figure 9-5.
The service is usually configured to participate in the playback transaction. If the playback transaction aborts (usually due to service-side exceptions), the message rolls back to the queue, where WCF detects it and dispatches it again to the service. This, in effect, yields an auto-retry mechanism. Consequently, you should keep the service’s processing of the queued call relatively short, or risk aborting the playback transaction. An important observation here is that it is wrong to equate queued calls with lengthy asynchronous calls.
As just demonstrated, assuming transactional queues, there are actually three transactions involved in every queued call: client, delivery, and playback, as shown in Figure 9-6.
From a design perspective, you rarely, if ever, depict the
delivery transaction in your design diagrams and you simply take it
for granted. In addition, the service will never participate in the
client’s transaction, so in effect my four logical transactional modes
from Chapter 7 (Client, Client/Service, Service,
None) do not apply with queued services. Configuring the service
contract operation with TransactionFlowOption.Allowed
or TransactionFlowOption.NotAllowed
leads to
the same result—the client transaction is never provided to the
service. Not only that, but TransactionFlowOption.Mandatory
is
disallowed for configuration on a queued contract, and this constraint
is verified at the service load time. The real issue is the relation
between the playback transaction and the service transactional
configuration.
From a WCF perspective, the playback transaction is treated as
the incoming transaction to the service. To participate in the
playback transaction, the service needs to have the operation
behavior configured with TransactionScopeRequired
set to true
, as shown in Example 9-8 and graphically in
Figure 9-5.
Example 9-8. Participating in the playback transaction
[ServiceContract] interface IMyContract { [OperationContract(IsOneWay = true
)] void MyMethod(); } class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true
)] public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction.TransactionInformation.DistributedIdentifier != Guid.Empty
); } }
An interesting point made in Example 9-8 is that with both MSMQ 3.0 and MSMQ 4.0, every transaction always uses the DTC for transaction management, even in the case of a single service and a single playback. This might change in the next release of WCF and the .NET Framework.
If the service is configured for not having any transactions (like the service shown in Example 9-9), WCF will still use a transaction to read the message from the queue, except that transaction will always commit (barring an unforeseen failure in MSMQ itself). Exceptions and failures at the service itself will not abort the playback transaction.
Example 9-9. Ignoring the playback transaction
[ServiceContract] interface IMyContract { [OperationContract(IsOneWay = true)] void MyMethod(); } class MyService : IMyContract { public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction == null); } }
This scenario is depicted graphically in Figure 9-7.
Services that do not participate in the playback transaction will not have the benefit of automated retries by WCF in the case of a playback failure, and it is possible for the played-back call to fail while the de-queued transaction commits. The main motivation for configuring queued services this way is to accommodate lengthy processing. If the service does not participate in the playback transaction, the call can take any amount of time to complete.
You can also write a service so that it manually requires a new transaction, as shown in Example 9-10.
Example 9-10. Using a new transaction
class MyService : IMyContract { public void MyMethod() { using(TransactionScope scope = new TransactionScope()) { ... scope.Complete(); } } }
This scenario is depicted in Figure 9-8.
When the service uses its own new transaction for each
message, it should also prevent
participating in the playback transaction (by defaulting to
the Transaction
Scope
Required
value of false
) so as not to affect the playback
transaction in any way. Again, this negates the benefit of the
auto-retry mechanism. However, having a new transaction separate
from the playback transaction gives the service the opportunity to
perform its own transactional work. You would typically configure a
service to use its own transaction when the queued operation being
called is nice to have and should be performed under the protection
of a transaction, yet does not need to be retried in case of a
failure.
The MSMQ queues described so far were both durable and transactional. The messages persisted to the disk, and posting a message to and reading it from the queue was transactional. However, MSMQ also supports nontransactional queues. Such queues can be durable and persist on the disk or can be volatile (stored in memory). If the queue is volatile, the messages in the queue will not persist across a machine shutdown or a machine crash or just recycling of the MSMQ service.
When you create a queue (either using the MSMQ administration tool or programmatically), you can configure it to be transactional or not, and that selection is fixed for the life of the queue. Nontransactional queues do not offer any of the benefits of transactional messaging systems, such as auto-cancellation, guaranteed delivery, and auto-retries. When using a nontransactional queue, if the client transaction aborts, the message or messages will stay in the queue and be delivered to the service. If the playback transaction aborts, the messages will be lost.
As inadvisable as it is, WCF can work with nontransactional
queues. MsmqBindingBase
(the
base class of NetMsmqBinding
)
offers the two Boolean properties Durable
and ExactlyOnce
, and these properties default to
true
:
public abstract class MsmqBindingBase : Binding,... { public bool Durable {get;set;} public bool ExactlyOnce {get;set;} //More members } public class NetMsmqBinding : MsmqBindingBase {...}
To work with a nontransactional queue, the ExactlyOnce
property must be set to false
. This will enable you to work both
with volatile and durable queues. However, because of the lack of
guaranteed delivery, when using a volatile queue WCF requires that you
set the ExactlyOnce
property of the
binding to false
; otherwise, WCF
will throw an InvalidOperationException
at the service
load time. Consequently, here is a consistent configuration for a
volatile nontransactional queue:
<netMsmqBinding> <binding name = "VolatileQueue" durable = "false" exactlyOnce = "false" /> </netMsmqBinding>
13.58.132.97