BizTalk 2004 has some very useful behavior around parallel execution and scope-level
timeouts that it is helpful to have a good understanding of.
What follows is a series of experiments & associated findings that should shed
some light on this area.
One restriction when using the parallel shape is that if multiple branches make use
of a given orchestration variable, you can get a compiler error:
error X2226: 'someVar': if shared data is updated in a parallel then all references
in every task must be in a synchronized or atomic scope
In the experiment below, we are accesing ‘someVar’ in both parallel branches
– each within an expression shape that also calls Thread.Sleep (the left hand for
10 seconds, the right hand for 30.) To address the compiler error just noted,
we have a scope around each usage with the ‘Synchronized’ flag on the
scopes set to ‘true’.
The trace messages shown to the right of the diagram tell us that, indeed, the execution
of Scope 2 and Scope 3 is completely serialized (in this case, Scope 2 completes before
Scope 3 begins.)
This experiment also tells us something unrelated (but useful): that an exception
will not interrupt “user code”. By “user code”
we refer to code in an expression shape, i.e. not using an orchestration “intrinsic”
such as a standard Delay shape or a Receive shape, etc. Notice via the timings
that the exception thrown in the left-hand branch doesn’t abort the Thread.Sleep
in the right-hand branch. The exception is caught only after the Thread.Sleep
in the right-hand branch completes (though the final trace message in the right hand
branch – ‘after 30 sec’ – does not execute.) This is
important to understand if you have expression shapes in orchestrations which are
making blocking calls to .NET components, DCOM servers, etc.
If we eliminate the ‘someVar’ reference in the expression shapes above,
we find that the scopes do not execute serially – whether the scope synchronization
flag is set to true OR false. Notice below (via the timings) that the
sleep operations are executed at the same time – so it is the presence of the common
variable in the expression that forces the synchronization!
We now have a 20 second gap after sleeping for 10 seconds (because the exception we
throw still doesn’t interrupt us).
We would like to use a given instance of a .NET object without imposing the
use of synchronized scopes. As it happens, if we have a .NET object that is
pointed at by two references (i.e. someVar and someVar2 – where someVar2 was set equal
to someVar with a simple assignment), the requirement that the orchestration compiler
normally imposes regarding the use of a synchronized scope goes away.
In the orchestration below, the left branch is using someVar, and the right branch
uses someVar2. The trace indicates that the sleep operations happen at same
time (though once again the exception doesn’t interrupt the right-hand side
and is caught only when the right-hand completes.)
Lesson: If you have a .NET component (that you know to be thread-safe) you wish to
use in an orchestration – and you wish to use a single instance of it – you will need
to have multiple references to that same instance to use in each branch of a parallel
shape. (The synchronized-scope alternative is likely unacceptable!) The
first variable declaration of your component might use the default constructor, while
the others will have “Use Default Constructor” set to false and will be
assigned to the first.
Now, there is more to be said regarding exceptions and what they will interrupt in
parallel branches. If instead of using a Thread.Sleep we use a Delay shape (or
a Receive shape, etc.) we find that throwing an exception in one branch of the parallel
shape will indeed interrupt the other branch(es). Notice in the timings below
that the Delay 30sec shape does not complete once the exception in the left-hand
branch in thrown. Particularly when you are structuring real-world business
flows, this is an important and quite useful behavior.
The behavior of exceptions in parallel flows is closely related to another behavior
in BizTalk 2004 – that of what BizTalk is prepared to “interrupt” when a long-running
scope has exceeded the timeout value that has been configured for the scope.
A Delay shape (or Receive, etc.) will indeed be interrupted when the timeout expires
(and a TimeoutException will be thrown). (Note that for an atomic scope, the
timeout governs the maximum time to allow prior to aborting the transaction.)
See the timings below and note that the Delay 30sec shape does not complete.
On the other hand, as you might expect at this point, a blocking call in an expression
shape (like a Thread.Sleep or a DCOM call, etc.) will not be interrupted.
However, the exception will be raised when the blocking call eventually returns:
- The Synchronized flag on a scope will only cause synchronized (serialized) behavior
among “peers in parallel branches” when shared variable or message state
is involved. (This is ignoring any transactional semantics you might layer on
top – which is beyond the scope of this article!)
- If you have a thread-safe .NET component that you wish to use in an orchestration
from multiple parallel branches, strongly consider having multiple variables point
to a common instance. The first instance might have “Use Default Constructor
= true”, while the remaining variables will have that flag set equal to false
and be assigned to the first instance in an expression shape:
someVar2 = someVar1;
someVar3 = someVar1;
someVar4 = someVar1, etc.
An alternative is to use scope-local variables that are assigned to a global instance.
- A line of code in an Expression shape will not be interrupted by either an
exception in a parallel branch or a TimeOutException arising from a timed-out
- A Delay shape or Receive shape, etc. will be interrupted by either an exception
in a parallel branch or a TimeOutException arising from a timed-out scope.