Understanding ParallelActivity in Windows Workflow

You might think that the ParallelActivity in Windows Workflow Foundation is misnamed.  At the very least, the ParallelActivity within Windows Workflow Foundation is deceptive.  It would seem that you can just place any activity within its branches and each branch will be executed, well, in parallel to each other.  However, that's not necessarily the case.  Consider a workflow that looks like this:

Two synchronous activities within a ParallelActivity - Total processing time is 8 seconds

The code behind each of these is very simple.

         private void oncode1(object sender, EventArgs e)
        {
            System.Threading.Thread.Sleep(3000);
            Console.WriteLine("Code 1");
        }

        private void oncode2(object sender, EventArgs e)
        {
            System.Threading.Thread.Sleep(5000);
            Console.WriteLine("Code 2");
        }

Even though each CodeActivity is contained within a branch of the ParallelActivity, the total time to process this workflow is 8 seconds (3 seconds for the left, then 5 seconds for the right) instead of the expected 5 seconds.  From experience, this behavior throws a lot of people off.

The reason why the total processing time is 8 seconds instead of 5 seconds is because the CodeActivity executes synchronously.  To be more precise, the CodeActivity does all of its work in its Execute method and then returns an ActivityExecutionStatus value of Closed.  The ActivityExectuionStatus state machine is described in the article Simplify Development With The Declarative Model Of Windows Workflow Foundation:

Activity State Machine 

Had the CodeActivity returned ActivityExecutionStatus.Executing and continued doing its work, we would see very different behavior.  In fact, you can read more about how to create an activity that returns ActivityExecutionStatus.Exeucting in my blog post, Use Workflow to Invoke Web Services in Parallel

Windows Workflow Foundation is a scheduled environment.  WF will schedule the ParallelActivity to execute, which in turn schedules its left branch to execute.  The right branch is not scheduled to execute until the left branch completes or yields.  That point is hard to digest, so let's look at a different example. 

Two asynchronous activities - total processing time is 5 seconds

In this workflow, the left DelayActivity has a TimeoutDuration of 3 seconds, while the right DelayActivity has a TimeoutDuration of 5 seconds.  In other words, we have 2 activities that perform their work asynchronously.  The left branch is scheduled, which immediately yields its execution, then the right branch is scheduled. 

The InvokeWebServiceActivity is similar to the CodeActivity because it also performs its work synchronously.  What happens if we put an InvokeWebServiceActivity in the left branch that calls a web service that does some work for 3 seconds? 

Sync activity to the left of an async activity - total processing time is 8 seconds

Just like we saw previously, the total processing time is 8 seconds.  The reason is because the activity in the left branch does not yield its execution to the workflow scheduler until it is complete.  Once the branch on the left either yields or completes, the branch on the right is scheduled.  Which brings up an interesting scenario... what if we switch the order of the activities within the ParallelActivity?

Async activity in left branch of ParallelActivity - total processing time is 5 seconds 

Here, the DelayActivity has a TimeoutDuration of 3 seconds and the InvokeWebServiceActivity does some work that takes 5 seconds.  The total time to process this workflow is only 5 seconds!  The reason why is because the left branch is scheduled first, and the DelayActivity performs its work asynchronously.  That means it yields execution status to the workflow scheduler so that it can, in turn, schedule the right branch of the ParallelActivity.  Internally, the DelayActivity returns ActivityExecutionStatus.Executing in its Execute method and completes its work later.

OK, easy enough to wrap your head around.  So long as the activity on the left does its work asynchronously, you get the perception of parallelism.  But what if the activities that you are using do not perform their work asynchronously?  It turns out that the designers of WF had this in mind when they created an activity called InvokeWorkflowActivity.  InvokeWorkflowActivity performs its work asynchronously. 

InvokeWorkflowActivity in a ParallelActivity

The InvokeWorkflowActivity calls another workflow where its CodeActivity does some work for 3 seconds.  The DelayActivity has a TimeoutDuration of 5 seconds.  The total processing time is 5 seconds, because the left branch performs its work asynchronously and yields to the right branch.

I say this with a word of caution... InvokeWorkflowActivity is a potential solution because that activity is invoked asynchronously, but the InvokeWorkflowActivity does not return ActivityExecutionStatus.Executing... it returns ActivityExecutionStatus.Closed.  In other words, you have no idea if the workflow that you invoke is still running or has completed.  Some folks want this to act differently, enabling you to execute another workflow synchronously.  Jon Flanders has an excellent example of invoking workflows synchronously on his blog.

For further reading:

Parallelism in Windows Workflow Foundation (WF)

A Workflow Sample - shows how to invoke workflows synchronously

Use Workflow to Invoke Web Services in Parallel

Simplify Development With The Declarative Model Of Windows Workflow Foundation - Explains the ActivityExecutionContext

Couple of custom activities that you can use to synchronize branches of a ParallelActivity