Windows Workflow (WF4) Task Parallelism with Sequences

In my last post The Workflow Parallel Activity and Task Parallelism I said that you can implement Task Parallelism with Windows Workflow Foundation.  In this post I want to explore some of the differences between the Parallel Activity in System.Workflow.Activities (WF3) and the Parallel Activity in System.Activities.Statements (WF4).

This post is a part of a series on WF, Parallelism and Threading

  1. Windows Workflow Foundation (WF4) Activities and Threads
  2. The Workflow Parallel Activity and Task Parallelism
  3. Windows Workflow (WF4) Task Parallelism with Sequences
  4. Windows Workflow Foundation (WF4) ParallelFor Activity
  5. Windows Workflow Foundation (WF4) - Parallel and ParallelFor Behavior (sample)

Windows Workflow Foundation 3 (WF3) Parallel Activity

One of the big changes between System.Workflow (WF3) and System.Activities (WF4) involves how a Parallel with a Sequence behaves.  In Workflow 3.5 the Parallel activity would execute an activity from each branch as it worked through the set of activities.


In this workflow, I’ve written to the console from each code activity so we can see the flow and what thread it was executed on.

    1: private void codeActivity11_ExecuteCode(object sender, EventArgs e)
    2: {
    3:     Console.WriteLine("[{0}] 1.1", Thread.CurrentThread.ManagedThreadId);
    4: }

When I run the app you can see how the execution pattern flows from left to right.


Windows Workflow Foundation 4 (WF4) Parallel Activity

In stark contrast to this behavior, the WF4 Parallel activity follows a very simple pattern.  It schedules it’s child activities and then it allows the child activities to do whatever they want to do in terms of scheduling their own children.  This is a simple but powerful concept that flows throughout WF4.  This is what allows you to compose activities out of any other kind of activity.  You could even have a Parallel Activity which contains a Sequence, a Flowchart and a State Machine.


This only works because the Parallel doesn’t know (and doesn’t care) how the child activities schedule their children.  To drill further into this, consider a Parallel with 3 sequences.


The Parallel activity has only 3 direct children.  It schedules Sequence 1, Sequence 2 and Sequence 3 and then they are executed as they pop off the stack.  As each child activity completes, the Parallel Activity schedules the Completion Condition for evaluation.  In the tracking data you will notice a VisualBasicValue<Boolean> is scheduled and then executes.  If this expression evaluates to True then the Parallel Activity will cancel the other branches (Sequence 2 and Sequence 3), otherwise Sequence 2 and Sequence 3 will start their execution in turn.


What happens if one of the activities in the sequence is goes idle?

In the following example, the middle row of activities are configured to delay for 1 millisecond causing an idle. In each Sequence, as the middle activity becomes idle, the Sequence will be blocked by a pending bookmark so control will pass to the next Sequence until all the Sequences are blocked waiting for a bookmark.

In the diagram below, Activity 1.2 has resumed after the delay and Activity 1.3 is now executing.


Each branch will resume (in no particular order) until the workflow completes it’s work.  The output of this example shows how control passes between the activities in the sequence.

[14] Test Thread

[18] 1.1

[18] Start 1.2 (Idle)

[18] 1.2 (Idle) Idle

[18] 2.1

[18] Start 2.2 (Idle)

[18] 2.2 (Idle) Idle

[18] 3.1

[18] Start 3.2 (Idle)

[18] 3.2 (Idle) Idle

[18] End 1.2 (Idle)

[6] 1.3

[18] End 2.2 (Idle)

[18] 2.3

[18] End 3.2 (Idle)

[18] 3.3


The Parallel Activity is designed to do work as long as there is work to do.  It simply schedules it’s child activities which will then schedule whatever their children are according to it’s design.

You can optimize for concurrent work by simply ensuring that if you are doing I/O that you are doing it asynchronously.  Our I/O activities are already designed to do this so by default you should get optimal behavior.  If you are writing your own activities, you can do Asynchronous operations by using the AsyncCodeActivity (see Creating Asynchronous Activities in WF) or by using a NativeActivity with a NoPersistHandle.

Happy Coding!

Ron Jacobs

Twitter: @ronljacobs