5

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.

4
  • Don't do that. You can't use asynchronous calls inside Parallel.ForEach Commented Jun 3, 2022 at 10:06
  • Have you seen the stacktrace of the exceptions? I bet Main is absent. Commented Jun 3, 2022 at 10:24
  • @shingo, you right, the {Main} is absent, but but main thread fall and this is confusing me Commented Jun 3, 2022 at 10:39
  • @Jonik main thread fall because these exceptions are unhandled, you can catch them with AppDomain.UnhandledException. Commented Jun 3, 2022 at 10:46

1 Answer 1

5

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();
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for reply! But I still have some misunderstud. The main question for me is why application goes in break mode? I thought, that exception inside delegate should be gone with it's thread, but exceptions pops up to unhandled and breaks the app domain. I expected the behaviour similar to Task.Run(async ()=> throw new Exception()), but it's kinda "similar" to await Task.Run(async ()=> throw new Exception())
@Jonik you're using the debugger and blocking the application from exiting. Without that Console.ReadLine() the application would exit immediately. The debugger breaks inside the worker task, not the outer exception. In production there wouldn't be anything to inspect that exception.
this code is just example, instead of Console.ReadLine(), you can use while (true) { Thread.Sleep(1000); } and application break too. I want to understand, how/why exception can pops up to unhandled and break the application. Excuse me if I can't explain the question correct, the english isn't native

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.