1

Created a simple program using Linqpad, where I am throwing an exception explicitly in the Parallel Foreach loop, which ideally shall be caught in the caller as Aggregate Exception, but when I explicitly throw the exception, it sometimes skip out few exceptions on random basis. I am not able to understand the behavior, anyone who can explain:

void Main()
{
    try
    {
      var intList = new List<int> {1,2,3,4,5,6};

      Parallel.ForEach(intList, i => Test1(i));
    }
    catch (AggregateException aggregateException)
    {
        foreach (var ex in aggregateException.Flatten().InnerExceptions)
        {
            ex.Message.Dump();
        }
    }
}

public void Test1(int i)
{
    try
    {
        if (i % 2 != 0)
            throw new Exception($"{i} - Odd value exception");

    }
    catch(Exception ex)
    {
        ex.Message.Dump();
        throw;
    }
}

public void Test2(int i)
{
        if (i % 2 != 0)
            throw new Exception($"{i} - Odd value exception");
}

public void Test3(int i)
    {
        try
        {
            if (i % 2 != 0)
                throw new Exception($"{i} - Odd value exception");

        }
        catch(Exception ex)
        {
            ex.Message.Dump();
        }
    }

Details:

  1. There two versions of Test, one with explicit Try Catch and other without
  2. Both have similar inconsistent behavior to the extent that in Test1, even local try catch doesn't print the value
  3. There can be third version Test3 which always work as exception is not explicitly thrown out of the parallel loop
  4. Dump is a linqpad print call replace it by Console.WriteLine on the visual studio

There's an option define here, which collects all exceptions in a ConcurrentQueue and throw them later as aggregated exception, but why the current code doesn't work as expected, I am not very sure. In this case we expect Output to be:

1 - Odd value exception
3 - Odd value exception
5 - Odd value exception

but some of them are randomly skipped, that too in a simple program, there are much more miss in a complex program, which do far more work

Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • @MickyD No i don't , this is purely for learning, otherwise pattern in the MSDN link is good – Mrinal Kamboj Sep 13 '18 at 08:57
  • Excellent. Thanks –  Sep 13 '18 at 09:04
  • `Parallel.For/Foreach` is meant for *data parallelism*. It creates roughly as many worker tasks as there are cores, partitions the data and feeds each partition to a task. It doesn't make sense of computation to proceed if any of those tasks throws. – Panagiotis Kanavos Sep 13 '18 at 11:06

1 Answers1

3

This is entirely expected behaviour.

See the docs,

an unhandled exception causes the loop to terminate immediately

When you throw an exception, no new Tasks will be scheduled.

So the behaviour will appear unpredictable. You have no right to expect that all subtasks will execute. That is not the contract of a Parallel.For loop.

The difference will be much clearer when you add more items to the source list. The output will always show a number of exceptions in the neighbourhood of ThreadPool.MinThreads.

bommelding
  • 2,969
  • 9
  • 14
  • You mean the reason for non printing is Parallel loop itself exiting, which then shall not be the case for explicit Tasks – Mrinal Kamboj Sep 13 '18 at 08:50
  • 1
    I don't know what you mean with 'explicit Tasks' but I think yes. See the link in my edit. – bommelding Sep 13 '18 at 08:53
  • `Task` as in TPL Task, which are not same as Parallel loop and are surely executed – Mrinal Kamboj Sep 13 '18 at 08:56
  • @MrinalKamboj there is not necessarily a 1:1 per `Parallel.ForEach` iteration and a `Task`. _"[Parallel.For tries to use the minimum number of tasks necessary to complete the loop as fast as possible. A better mental model includes Parallel spinning up tasks as threads are available to process those tasks, and each of those tasks then participating in a range management scheme](https://blogs.msdn.microsoft.com/pfxteam/2009/05/26/does-parallel-for-use-one-task-per-iteration/.)"_ –  Sep 13 '18 at 09:00
  • @MrinalKamboj Parallel.Foreach uses tasks as *workers*. It partitions the data and feeds each partition to a different worker. It's meant for data parallelism which is why it uses roughly as many workers as there are cores to ensure the CPU is kept fully occupied. On a 4-core machine it would create 4 tasks to process 800K items, passing 200K of them to each worker. If it *didn't* the CPU would waste all its time switching from one task to another instead of processing – Panagiotis Kanavos Sep 13 '18 at 11:08