Ordered Parallel Execution in .net

One of our applications uses a BlockingCollection<func<string>>to modify the workflow based on the user’s selection. Once we had the collection, we executed all functions in the order of which they were added to the list. This worked fine until some of the vendor services used in the functions started to slow down.

Since we have no control over the vendor’s services, the next best solution is to run all of these functions in parallel to speed up the process, but since Parallel.ForEach doesn't guarantee the order of execution I ended up creating a Plinq query of the BlockingCollection, parallelizing, ordering and then executing the result.

The functions:

public static Func<string> Func1 = () =>
     {
         Console.WriteLine("F1 with thread Id {0}", Thread.CurrentThread.ManagedThreadId );
       return   " Func1 Called";
     };
     public static Func<string> Func2 = () =>
     {
         Console.WriteLine("F2 with thread Id {0}", Thread.CurrentThread.ManagedThreadId);
         return " Func2 Called";
     };
     public static Func<string> Func3 = () =>
     {
         Console.WriteLine("F3 with thread Id {0}", Thread.CurrentThread.ManagedThreadId);
         return " Func3 Called";
     };
     public static Func<string> Func4 = () =>
     {
         Console.WriteLine("F4 with thread Id {0}", Thread.CurrentThread.ManagedThreadId);
         return " Func4 Called";
     };
     public static Func<string> Func5 = () =>
     {
         Console.WriteLine("F5 with thread Id {0}", Thread.CurrentThread.ManagedThreadId);
         return " Func5 Called";
     };
     public static Func<string> Func6 = () =>
     {
         Console.WriteLine("F6 with thread Id {0}", Thread.CurrentThread.ManagedThreadId);
         return " Func6 Called";
     };

The method used to create the query then execute all functions in order:

static void Main(string[] args)
{
    using (var funcs = new BlockingCollection<Func<string>> { Func1, Func3, Func2, Func4, Func6, Func5 })
    {
        funcs.
            Select((value, index) => new
        { index, value = value() })
                                       .AsParallel()
                                       .AsOrdered()
                                       .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
                                       .WithDegreeOfParallelism(10)
                                       .ForAll(item =>
        {
            Console.WriteLine(item.index + item.value);
        });
    }
}

Note that when the above code executes the order will be preserved until ForAll is executed as it will parallelize the query results.

Performance Note:

This technique is associated with a a bit of performance price; I strongly recommend performing as many tests as possible on a test or staging environment before moving to client facing production environment.

Comments (5) -

Mike Miller 5/22/2014 4:09:12 PM

Is this thread safe?

Mike,
The Funcs I am using in the demo don't have any state change, so they are thread safe.
If your Funcs or Actions change state, you need to incorporate thread safety code yourself.

Sammy

Mike Miller 5/24/2014 1:01:23 AM

Thank you.

Can I use this without projecting the query into an anonymous object?

Jon,
No you cannot, the projection is where the execution of the Funcs takes please.
You can create your own object or use a native class like the PairValue msdn.microsoft.com/.../5tbh8a42(v=vs.110).aspx

Add comment