I observed a weird behavior while experimenting with a PLINQ query. Here is the scenario:
- There is a source
IEnumerable<int>sequence that contains the two items 1 and 2. - A Parallel LINQ
Selectoperation is applied on this sequence, projecting each item to itself (x => x). - The resulting
ParallelQuery<int>query is consumed immediately with aforeachloop. - The
selectorlambda of theSelectprojects successfully the item 1. - The consuming
foreachloop throws an exception for the item 1. - The
selectorlambda throws an exception for the item 2, after a small delay.
What happens next is that the consuming exception is lost! Apparently it is shadowed by the exception thrown afterwards in the Select. Here is a minimal demonstration of this behavior:
ParallelQuery<int> query = Enumerable.Range(1, 2)
.AsParallel()
.Select(x =>
{
if (x == 2) { Thread.Sleep(500); throw new Exception($"Oops!"); }
return x;
});
try
{
foreach (int item in query)
{
Console.WriteLine($"Consuming item #{item} started");
throw new Exception($"Consuming item #{item} failed");
}
}
catch (AggregateException aex)
{
Console.WriteLine($"AggregateException ({aex.InnerExceptions.Count})");
foreach (Exception ex in aex.InnerExceptions)
Console.WriteLine($"- {ex.GetType().Name}: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
Output:
Consuming item #1 started
AggregateException (1)
- Exception: Oops!
Chronologically the consuming exception happens first, and the PLINQ exception happens later. So my understanding is that the consuming exception is more important, and it should be propagated with priority. Nevertheless the only exception that is surfaced is the one that occurs inside the PLINQ code.
My question is: why is the consuming exception lost, and is there any way that I can fix the query so that the consuming exception is propagated with priority?
The desirable output is this:
Consuming item #1 started
Exception: Consuming item #1 failed