Seapoe
Seapoe

Reputation: 479

IProgress<T> and Parallel.ForEach Sync Issues

I'm running into a sync issue involving reporting progress inside of a Parallel.ForEach. I recreated a simplified version of the problem in a Console App. The example actually only uses one item in the list. Here's the code:

 class Program
{
    static void Main(string[] args)
    {
        int tracker = 0;

        Parallel.ForEach(Enumerable.Range(1, 1), (item) =>
        {
                var progress = new Progress<int>((p) =>
                {
                    tracker = p;
                    Console.WriteLine(String.Format("{0}", p));
                });

                Test(progress);
         });


        Console.WriteLine("The last value is: {0}", tracker);
        Console.ReadKey();
    }

    static void Test(IProgress<int> progress)
    {
        for (int i = 0; i < 20; i++)
        {
            progress.Report(i);
        }
    }
}

enter image description here

As you can see, the line I expect to see last isn't output last and doesn't contain 20. But if I remove progress reporting and just write to output in the for loop like this:

class Program
{
    static void Main(string[] args)
    {
        int tracker = 0;

        Parallel.ForEach(Enumerable.Range(1, 1), (item) =>
        {
            tracker = Test();
        });


        Console.WriteLine("The last value is: {0}", tracker);
        Console.ReadKey();
    }

    static int Test()
    {
        int i;
        for ( i = 0; i < 20; i++)
        {
            Console.WriteLine(i.ToString());
        }
        return i;
    }
}

enter image description here

it behaves like I expect. As far as I know, Parallel.ForEach creates a Task for each in item in the list and IProgress captures the context in which it's created on. Given it's a console app I didn't think that would matter. Help please!

Upvotes: 1

Views: 506

Answers (1)

Charles Mager
Charles Mager

Reputation: 26223

The explanation is pretty much exactly what's written in the docs:

Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.

By using Progress<T>.Report you're effectively queueing 20 tasks on the thread pool. There's no guarantee as to what order they're executed in.

Upvotes: 2

Related Questions