3

Why does the following crash if run inside a console application instead of throwing an AggregateException and being caught by the outer try/catch?

I've simplified the use case for the await for brevity, but in the relevant code I am indeed trying to execute an awaitable Task of importance.

var list = new List<string>() {"Error"};

        try
        {
            Parallel.ForEach(list, new ParallelOptions()
            {
                MaxDegreeOfParallelism = 8
            }, async listEntry =>
            {
                await Task.Delay(5000);
                throw new Exception("Exception");
            });
        }
        catch (Exception ex)
        {
            //never hits, the application crashes
        }

        Console.ReadLine();

I note that the following does not cause the application to fail, and an exception is indeed caught, but I do not understand what's really going on as to what is fundamentally different about the two contexts:

var list = new List<string>() {"Error"};

        try
        {
            Parallel.ForEach(list, new ParallelOptions()
            {
                MaxDegreeOfParallelism = 8
            }, listEntry =>
            {

                throw new Exception("Exception");
            });
        }
        catch (Exception ex)
        {
            //exception is caught, application continues
        }

        Console.ReadLine();
Grant H.
  • 3,689
  • 2
  • 35
  • 53
  • I don't see any code that catches exception on async part of your tasks - which indeed leads to unhanded exceptions and termination of process... Side note: I'm not really sure why you need `Parallel.ForEach` to start multiple tasks when you don't care how they end... You may search for "C# fire-and-forget async" if you actually need code similar to one in this post. – Alexei Levenkov Jul 08 '16 at 00:49
  • @AlexeiLevenkov, I guess my question is...why do I need to catch it inside of the loop simply because it's asychronous? When it's synchronous, it does not require that. Also, this isn't for a fire and forget, in my actual code I'm making a call to a function that is awaitable, I just simplified the use case for brevity with an empty task. – Grant H. Jul 08 '16 at 00:52
  • 3
    Don't mix `Parallel.ForEach` with `async/await` http://stackoverflow.com/questions/11564506/nesting-await-in-parallell-foreach –  Jul 08 '16 at 00:58
  • @GrantH. if you are not `await`-ing them it is fire-and-forget (broken in the way you see it). If your actual code has `await` - makes sure to update the code. (Note: I think `Task.Delay(1)` is better and more compact way to demonstrate awaitable code than `Task.Run`.) – Alexei Levenkov Jul 08 '16 at 01:06
  • @AlexeiLevenkov, I am awaiting it though (am I not?), but point taken on using `Task.Delay()`, I've updated my example. – Grant H. Jul 08 '16 at 01:08
  • @GrantH. You are not awaiting *result* of your asynchronous method/lambda. You should easily observe it yourself if you set delay for very long time and debug the code - loop will end *before* any exception is thrown. Some links from http://stackoverflow.com/questions/22349210/how-async-and-await-works could help in understanding the problem (feel free to write up self-answer at some point if you figure out how to properly explain it) – Alexei Levenkov Jul 08 '16 at 01:11
  • (BTW proper solution for your problem but not duplicate as it does not explain what you see - `Task.WhenAll` - http://stackoverflow.com/questions/12343081/run-two-async-tasks-in-parallel-and-collect-results-in-net-4-5) – Alexei Levenkov Jul 08 '16 at 01:13
  • @AlexeiLevenkov, perhaps I'm being dense, my apologies, but when I set a delay of 5000ms, the code waits 5 seconds as expected and then throws the exception and crashes. I am awaiting the `Task.Delay(5000)`. The loop does not end early. – Grant H. Jul 08 '16 at 01:15
  • @Grant-h the reason you need to catch it when using Parallel.Foreach is that the tasks are running on different threads. This is not obviously apparent due to the simplification of code using Parallel.Foreach. When you do this same code without the Parallel.Foreach, its all running in the main thread, and therefore able to be caught by that thread. – MercifulGiraffe Jul 08 '16 at 01:16
  • @AlexeiLevenkov, `Task.WhenAll` is not appropriate for my particular case because I need to control the degree of parallelism explicitly. That said, I'm considering using an approach suggested in the link @MickyD referenced that may be clearer (though I suspect an uncaught exception would still cause the crash), http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx – Grant H. Jul 08 '16 at 01:20
  • @GrantH. Just put `Console.WriteLine("test");` right before `ReadLine`. It will print string before crashing. ... `ForEachAsync` you've linked to seem to be what you are looking for than - it will also propagate exceptions the way you expect (as all tasks are `await`-ed unlike in your sample). – Alexei Levenkov Jul 08 '16 at 01:22
  • @AlexeiLevenkov, thanks, I was indeed just being dense :) – Grant H. Jul 08 '16 at 01:23
  • 1
    @MickyD, thanks for the link, that's very helpful, seems like my whole approach is a bit off. – Grant H. Jul 08 '16 at 01:24
  • This is happening in my code as well. I am not using Async but I am reading from a File. What I did was divide up my work into 4 separate List. I then created 4 Tasks. One for each list. – CBFT Oct 14 '22 at 18:03

1 Answers1

8

As already mentioned in comments, you shouldn't mix async and Parallel.ForEach, they don't work together.

What you're observing is one of the consequences of that: the lambda is compiled as an async void method and when an async void method throws, the exception is rethrown on its SynchronizationContext, which will normally crash the application.

Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514