The Problem Solver

Tell me and I will forget
Show me and I will remember
Involve me and I will understand
- Confucius -

Google Ads

This Blog

Syndication

Search

Tags

News





  • View Maurice De Beijer's profile on LinkedIn

Community

Email Notifications

Explore

Archives

Using the ReplicatorActivity in Parallel mode

I have heard quite a few times that the ReplicatorActivity can only be use in parallel mode with a custom activity. The reason being that you need an extra property to store the current child data.

When the ReplicatorActivity works in sequential mode the following code works just fine:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    int value = (int)replicatorActivity1.CurrentChildData[replicatorActivity1.CurrentIndex];
    Console.WriteLine("Loop value = {0}", value);
}

However when run in parallel the current index always point to the last item so this doesn't work. As an aside I think this is a bug as it results in hard to find errors. IMHO the ReplicatorActivity CurrentIndex should be -1 or some other value that would trigger either an IndexOutOfRangeException or an InvalidOperationException. Unfortunately that is not the case Sad.

Instead when we set the mode to parallel we need to use the ChildInitialized event to save the child data for that loop. So we need a place to store it.

Traditional thinking results in an extra property being added that is being scoped to the loop and an extra property means a custom activity to add it to.

But wait a minute doesn't every workflow foundation activity derive from DependencyObject and isn't that class all about using dependency properties?

Yes it is and that is exactly the reason you don't need a custom activity. In fact you can add as much extra data to any activity as you like!

 

How to add custom data to the standard activities

The trick is in using a DependencyProperty that is created using the DependencyProperty.RegisterAttached() function. This creates an attached property you can use to store data in activities that have no clue about the data. So the first step is to create a dependency property like this:

public static DependencyProperty LoopValueProperty = 
    DependencyProperty.RegisterAttached("LoopValue", typeof(int), typeof(Workflow1));

Now in the ChildInitialized event we van store the loop value in child activity like this:

private void replicatorActivity1_ChildInitialized(
    object sender, 
    ReplicatorChildEventArgs e)
{
    e.Activity.SetValue(LoopValueProperty, e.InstanceData);
}

Easy right Smile

So if I add a code activity as the child just to print the data all I need is the following code:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    Activity activity = (Activity)sender;
    int value = (int)activity.GetValue(LoopValueProperty);
Console.WriteLine("The current value = {0}", value); }

A slightly larger example showing the parallel behavior

Below is a slightly more complex example showing this. I have added a DelayActivity with a random timeout just to show that the ReplicatorActivity is really running in parallel.

The workflow looks like this:

image

And when run this produces the following output:

image

The complete code behind file looks like this:

public sealed partial class Workflow1 : SequentialWorkflowActivity
{
    public Workflow1()
    {
        InitializeComponent();
    }

    public static DependencyProperty LoopValueProperty = 
        DependencyProperty.RegisterAttached("LoopValue", typeof(int), typeof(Workflow1));


    private void replicatorActivity1_Initialized(object sender, EventArgs e)
    {
        ReplicatorActivity replicator = (ReplicatorActivity)sender;
        replicator.InitialChildData = new List<int>(Enumerable.Range(1, 20));
    }

    private void delayActivity1_InitializeTimeoutDuration(object sender, EventArgs e)
    {
        DelayActivity delay = (DelayActivity)sender;
        Random random = new Random();
        delay.TimeoutDuration = TimeSpan.FromSeconds(random.Next(5));
    }

    private void replicatorActivity1_ChildInitialized(
        object sender, 
        ReplicatorChildEventArgs e)
    {
        e.Activity.SetValue(LoopValueProperty, e.InstanceData);
    }

    private void codeActivity1_ExecuteCode(object sender, EventArgs e)
    {
        Activity activity = (Activity)sender;
        int value = (int)activity.Parent.GetValue(LoopValueProperty);
Console
.WriteLine("The current value = {0}", value); } }

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Published Wed, Oct 22 2008 17:17 by Maurice
Filed under: , , ,

Comments

# re: Using the ReplicatorActivity in Parallel mode@ Friday, October 24, 2008 4:23 AM

Hello

Thanks for this article.

If I use a CallExternalMethod in the Replicator how can I pass the correct LoopValueProperty as parameter to the external method.

How can I adapt this example to support multithreading.

Each call to CallExternalMethod would run in a separate thread.

Thanks

by philippe

# re: Using the ReplicatorActivity in Parallel mode@ Tuesday, November 04, 2008 3:04 AM

Hello,

I'm using .Net 3.5 and have tried exactly reproduce your code, but on the folowing assignment

 private void replicatorActivity1_ChildInitialized(

       object sender,

       ReplicatorChildEventArgs e)

   {

       e.Activity.SetValue(LoopValueProperty, e.InstanceData);

   }

The exception: "Dependency Object type 'System.Workflow.Activities.SequenceActivity' doesn't define this DependencyProperty 'LoopValue:Workflow1'."

Where I'm wrong?

Thank you in advance.

by Max

# re: Using the ReplicatorActivity in Parallel mode@ Tuesday, November 04, 2008 3:20 AM

@Max,

Please make sure you create the dependency property using RegisterAttached() instead of Register(). That is the most likely error.

Maurice

by Maurice