.net and Parallel.ForEach. Exception bypass the "try" block

Viewed 83

Can somebody explain the behaviour of this code:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello world");
        try
        {
            Parallel.ForEach(new[] { 1, 2, 3 }, async i =>
            {
                await Task.Delay(TimeSpan.FromSeconds(3));
                throw new TaskCanceledException();
            });
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.WriteLine("Goodbye cruel world");
        Console.ReadLine();
    }
}

How can it be possible, the exception pops up in the main thread out of "try" block and application fall. I knew the best way for parallel async is "Task.WhenAll". The goal of the question is to understand the behaviour.

1 Answers

Parallel.ForEach can't be used to call asynchronous methods. It's only meant for in-memory data parallelism, not asynchronous operations. It works by partitioning its data and feeding each batch to a worker task, using roughly one worker per CPU core. It even uses the calling thread to process one of those partitions, which is why it seems to be "blocking".

None of its overloads accepts a Task which means the return type of the lambda in this case is async void.

The question's code is equivalent to :

async void Do(int i)
{
    await Task.Delay(TimeSpan.FromSeconds(3));
    throw new TaskCanceledException();
}

Parallel.ForEach(data,Do);

async void methods can't be awaited which means their exceptions can't be caught by the caller either. This code fires off 3 async void calls using and returns immediately.

In .NET 6 you should use Parallel.ForEachAsync instead :

var data=new[] { 1, 2, 3 };
await Parallel.ForEachAsync(data, async (i,token) =>
{
    await Task.Delay(TimeSpan.FromSeconds(3));
    throw new TaskCanceledException();
});
Related