Search code examples
c#.nettask-parallel-libraryparallel.foreachaggregateexception

Parallel.Foreach loop, inconsistent behavior with explicit throw statement


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


Solution

  • 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.