Reputation: 479
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);
}
}
}
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;
}
}
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
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