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 [:(].


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 [:)]


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!


[f1]
[f2]

3 thoughts on “Using the ReplicatorActivity in Parallel mode

  1. 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

  2. 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.

  3. @Max,

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

    Maurice

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>