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.
Experiment One:
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.


Experiment Two:
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).

Experiment Three:
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.


Experiment Four:
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.


Experiment Five:
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:


Summary:
-
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 scope.
-
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.