Plinq Aggregate extension with StringBuilder

375 views Asked by At

I've made some experimental Plinq query, and I'm not sure about the result can be corrupted or not.

Here is 3 different method, which provides the same result:

// unitTask is typeof Task<List<SomeEntity>>

        //sequential version PLINQ
        Console.WriteLine(unitTask.Result.Take(10)
           .Aggregate(new StringBuilder(),
           (text, current) => text.AppendFormat("@{0}sa{1}", 
               current.FullName.Substring(0, 3), 
               current.FullName.Substring(4)))
           .ToString());

        //parallel version PLINQ
        Console.WriteLine(unitTask.Result.Take(10).AsParallel()
            .Aggregate(new StringBuilder(),
            (text, current) => text.AppendFormat("@{0}sa{1}",
                current.FullName.Substring(0, 3),
                current.FullName.Substring(4)))
            .ToString());

        //parallel version foreach with Partitioner
        var output = new StringBuilder();
        Parallel.ForEach(Partitioner.Create(unitTask.Result.Take(10)), r =>
        {
            //Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            output.AppendFormat("@{0}sa{1}", r.FullName.Substring(0, 3), 
                r.FullName.Substring(4));
        });

        Console.WriteLine(output.ToString());

My questions are:

Can I use StringBuilder in PLINQ? Due to append method is not thread safe as I know.

Or does it run in sequential mode in this case?

Parallel.Foreach runs the query in different threads, but result is the same as sequential Plinq.

Is it accidental, or it's smart and uses some synchronization?

2

There are 2 answers

1
svick On BEST ANSWER
  1. This version doesn't use PLINQ, it uses the standard LINQ, so it's safe.

  2. This version uses an overload of Aggregate() that's not safely parallelizable, so it will also execute on a single thread. This means it's safe, but it also won't be any faster than the sequential version.

    To actually take advantage of PLINQ, you would need to use another overload of Aggregate() that actually can execute in parallel. In your case that would mean having a separate StringBuilder for each thread and then combine all the StringBuilders into one. Something like:

    input.AsParallel().Aggregate(
        () => new StringBuilder(),
        (text, current) => text.AppendFormat("@{0}", current.FullName),
        (text1, text2) => text1.Append(text2),
        text => text.ToString())
    

    This assumes that you don't care about the order of elements in the final string. If you do, this code won't work correctly.

  3. This code modifies the same StringBuilder object from multiple threads. StringBuilder is not thread-safe, so this code is not safe.

3
Panagiotis Kanavos On

It's accidental, probably because the code doesn't do all that much and may be running on a single thread. All your calls involde unitTask.Result which blocks until unitTask finishes. All snippets actually work on only 10 entities produced sequentially, so there is not enough data to justify parallel execution

The three snippets do different things:

  1. The first snippet simply processes a list of 10 snippets sequentially.
  2. The PLINQ version takes a list of 10 entities but doesn't do anything in parallel. Even if it did, the call to Aggregate collects the results from all workers and processes them sequentially to create the final result.
  3. The third snippet could display parallel behavior, as it executes an action block in parallel. Again, the number of results is too small and only one thread is used