Geeks With Blogs
David Paquette CDD (Caffeine Driven Development)

If you are like me, you may have been surprised when you read the MSDN documentation for System.Activities.Statements.Parallel.

A Parallel activity operates by simultaneously scheduling each Activity in its Branches collection at the start. It completes when all of its Branches complete or when its CompletionCondition property evaluates to true. While all the Activity objects run asynchronously, they do not execute on separate threads, so each successive activity only executes when the previously scheduled activity completes or goes idle. If none of the child activities of this activity go idle, this activity execute in the same way that a Sequence activity does.

-MSDN (http://msdn.microsoft.com/en-us/library/system.activities.statements.parallel.aspx)

While the name implies that each branch of activities would be executed on a separate thread, the Parallel activity does not execute activities on separate threads.  In fact, all activities in a workflow execute on the same thread.  This is very similar to how in Windows Forms and WPF, all code that manipulates the user interface must be executed on the UI thread.  You might be asking yourself:  What is the point of this parallel activity?

It is possible for some activity to execute work on a background thread.  When combined with the Parallel activity (or the ParallelForEach activity), this allows the workflow runtime to schedule other activities to be executed while it is waiting for the background thread to complete.  WF4 comes with a number of asynchronous activities that can be used in this way.  Some examples are the Delay, InvokeMethod (when RunAsynchronously is set to true).  If you are creating custom activities, you can also create activities that execute asynchronously.  If you are implementing a code-based activity, you can inherit from AsyncCodeActivity.  If you are implementing more complex activities that need to make use of the workflow runtime, there are a couple strategies for implementing asynchronous activities.  One option is to use the parallel extensions introduced in .NET 4 (Workflow and Parallel Extensions).   Another option is to use bookmarks.

However, if you want to compose a workflow in XAML and have that workflow executed in parallel, your options are more limited.  Here’s an example:

image

In this example, I would like the 2 workflows to execute on a separate thread.  The output, however, shows that both activities are executed on the same thread:

image

Luckily, there is a way to accomplish this using a dynamic activity to execute a child activity on a background thread.  The child activity can be any activity (including Sequence or FlowChart), so this allows us to execute any portion of a workflow on a background thread. 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Activities;
   6: using System.Threading.Tasks;
   7: using System.ComponentModel;
   8: using Microsoft.VisualBasic.Activities;
   9:  
  10: namespace ParallelActivities
  11: {
  12:     [Designer(typeof(AsyncActivityWrapperDesigner))]
  13:     public class AsyncActivityWrapper : AsyncCodeActivity
  14:     {
  15:         public AsyncActivityWrapper()
  16:         {
  17:             Body = new ActivityAction();
  18:         }
  19:  
  20:         [Browsable(false)]
  21:         public ActivityAction Body { get; set; }
  22:         
  23:         protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
  24:         {        
  25:              Activity activity = CreateDynamicActivity(context);
  26:             IDictionary<string,object> inputs = GetArgumentsAndVariables(context);
  27:             Task task = Task.Factory.StartNew((ignore) =>
  28:             {
  29:                 WorkflowInvoker.Invoke(activity, inputs);
  30:                 
  31:             }, state);
  32:             task.ContinueWith((t) => callback(t));
  33:             return task;
  34:         }
  35:  
  36:         protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
  37:         {            
  38:         }
  39:  
  40:         private Activity CreateDynamicActivity(AsyncCodeActivityContext context)
  41:         {
  42:             DynamicActivity result = new DynamicActivity();
  43:             //Create a DynamicActivityProperty for each argument / variable in the current context
  44:             foreach (PropertyDescriptor property in context.DataContext.GetProperties())
  45:             {
  46:                 DynamicActivityProperty dynamicActivityProperty = new DynamicActivityProperty();
  47:  
  48:                 dynamicActivityProperty.Name = property.Name;
  49:                 dynamicActivityProperty.Type = typeof(InArgument<>).MakeGenericType(property.PropertyType);
  50:                 dynamicActivityProperty.Value = Activator.CreateInstance(dynamicActivityProperty.Type);
  51:                 result.Properties.Add(dynamicActivityProperty);
  52:             }
  53:  
  54:             //Copy impors to dynamic activity;
  55:             VisualBasic.SetSettings(result, VisualBasic.GetSettings(this));
  56:             result.Implementation = () => Body.Handler;
  57:             return result;
  58:         }
  59:  
  60:         private IDictionary<string, object> GetArgumentsAndVariables(AsyncCodeActivityContext context)
  61:         {
  62:             IDictionary<string, object> result = new Dictionary<string, object>();
  63:  
  64:             foreach (PropertyDescriptor property in context.DataContext.GetProperties())
  65:             {
  66:                 result.Add(property.Name, property.GetValue(context.DataContext));
  67:             }
  68:  
  69:             return result;
  70:         }
  71:     }
  72: }

Using the AsyncActivityWrapper inside a Parallel activity accomplishes the desired behavior by executing activities inside the AsyncActivityWrapper as shown here:

image

With the AsyncActivityWrapper, we can see that each branch of the Parallel activity is executed on a separate thread:

image

The designer for this activity is actually very simple.  It contains a single WorkfowItemPresenter which allows us to drag and drop an activity into the AsyncActivityWrapper.

   1: <sap:ActivityDesigner x:Class="ParallelActivities.AsyncActivityWrapperDesigner"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   5:     xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
   6:  
   7:     <sap:WorkflowItemPresenter Margin="7" Item="{Binding Path=ModelItem.Body.Handler, Mode=TwoWay}" HintText="Drop Activity"/>
   8:  
   9: </sap:ActivityDesigner>

 

In conclusion, the AsyncActivityWrapper can be used to execute portions of a workflow on a separate thread.  When combined with the Parallel or ParallelForEach activities, this allows us to execute multiple activities in parallel.  In situations where a workflow is processing a large amount of data, or doing a large number of complex calculations, the AsyncActivityWrapper can help to improve performance.  The implementation provided here does have some limitations.  As with any parallel programming, you need to make sure that activities that are executing in parallel do not conflict with each other.  If the activities are trying to access and manipulate the same data, you might run into problems.  If the activities have a return value, you may need an alternate implementation of AsyncActivityWrapper that inherits from AsyncCodeActivity<TResult>.  Also, since each activity is being executed as a separate independant workflow, you will lose some workflow functionality such as Persistence and Tracking.

Posted on Tuesday, October 26, 2010 1:50 PM .NET , Workflow , WF4 , VS2010 | Back to top


Comments on this post: WF4 Asynchronous Workflows

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
This (the parallel activity and its ugly cousin the parallel for each activity from MS not this blog ) could be the stupidest thing Ive ever seen. I completely agree that it is indeed synchronous. This activity is completely friggin worthless.
Executing your async activity in a for-loop or any branching activity would accomplish the exact same thing.
When one reads "Parallel" in the name I expect Parallel execution.

Completely ridiculous!
Left by Parallel? How? on Nov 04, 2010 7:51 AM

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
I have not been able to get this to work. For a wf wcf service, if I place a sequence with a receive and send request, the recieve operation gets hidden when trying to add to a client.
Also on an assign activity, I get the following error: The following errors were encountered while processing the workflow tree:
'DynamicActivity': The private implementation of activity '1: DynamicActivity' has the following validation error: Compiler error(s) encountered processing expression ""KMS Account Creation has completed for " + config.COMPANY_CODE"
Left by Michael Rivera on May 08, 2012 11:41 AM

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
Why use the embedded text box with line #'s? It is not easy to copy your code. The line numbers are not helpful.
Left by Guy on Jul 15, 2012 4:53 AM

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
The Parallel and ParallelForEach are actually the smartest thing you've ever seen, you just don't know it. It intelligently decides how to parallelize activities, managing behavior on synchronous activities vs asynchronous activities vs activities parked on Bookmarks.

In order to see truly parallel behavior, your activities MUST be async (like derivatives of AsyncCodeActivity, or built-in activities like Delay).
Left by Craig B on Aug 14, 2012 12:58 AM

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
I have used this with an internal InvokeMethod activity and put the whole thing in a PickBranch. When another branch finishes early it doesn't cancel the AsyncActivityWrapper like I might expect.

I'm somewhat new to WF so any help would be appreciated.
Left by Damon Achey on Jan 06, 2014 1:40 PM

# re: WF4 Asynchronous Workflows
Requesting Gravatar...
AsyncActivityWrapper doesn't worked for parallel foreach
Left by chsy on Feb 02, 2015 10:04 PM

Your comment:
 (will show your gravatar)


Copyright © David Paquette | Powered by: GeeksWithBlogs.net