Ciaran
Ciaran

Reputation: 551

The AggregateException I catch does not have the exception I expect

I am using the Task Parallel Library to set up a chain of Tasks as shown below but I am getting an odd exception handling experience that I don't understand.

I use Parallel.ForEach and invoke an Action which includes a call to the following method. This Parallel.ForEach is wrapped in a try...catch(AggregateException) and when an exception occurs - as it does in one of the Parallel branches - a SchemaValidation exception then I would expect to see that in the AggregateException.

However, what I get is 'A task was canceled' - TaskCanceledException. Where has my SchemaValidationException gone?

        private static void ProcessChunk(Task<ISelectedChunk> selectionTask, 
                                     IRepository repository, 
                                     IIdentifiedExtractChunk identifiedExtractChunk, 
                                     IBatchRunConfiguration batchRunConfiguration, 
                                     IBatchRun batchRun, 
                                     ILog log, 
                                     IAuthenticationCertificate authenticationCertificate, 
                                     IFileSystem fileSystem,
                                     long batchRunRid)
    {
        var transformationTask = selectionTask.ContinueWith(TransformationFunction.Transformation(identifiedExtractChunk, batchRunConfiguration, batchRun),
                                                            TaskContinuationOptions.NotOnFaulted);

        var schemaValidationTask = transformationTask.ContinueWith(SchemaValidationFunction.SchemaValidationTask(batchRunConfiguration),
                                                                   TaskContinuationOptions.NotOnFaulted);

        var compressTask = schemaValidationTask.ContinueWith(CompressFunction.CompressTask(identifiedExtractChunk),
                                                             TaskContinuationOptions.NotOnFaulted);

        var encryptTask = compressTask.ContinueWith(EncryptionFunction.EncryptTask(authenticationCertificate),
                                                    TaskContinuationOptions.NotOnFaulted);

        var fileGenerationTask = encryptTask.ContinueWith(FileGenerationFunction.FileGenerationTask(identifiedExtractChunk, batchRunConfiguration, fileSystem),
                                                          TaskContinuationOptions.NotOnFaulted);
        // Take the time before we start the processing
        DateTime startBatchItemProcessing = DateTime.Now;

        // Start with the Selection Task
        selectionTask.Start();

        // And wait on the last task in the chain
        fileGenerationTask.Wait();

        // Take the time at the end of the processing
        DateTime endBatchItemProcessing = DateTime.Now;

        // Record all the relevant information and add it to the collection 
        IBatchChunkProcessed batchChunkProcessed = GetBatchItemProcessed(identifiedExtractChunk, batchRunRid, fileGenerationTask.Result, transformationTask.Result.Item2, startBatchItemProcessing, endBatchItemProcessing);
        BatchItemsProcessed.Add(batchChunkProcessed);

Upvotes: 4

Views: 1691

Answers (1)

svick
svick

Reputation: 244837

Let's simplify your code a bit:

var t1 = Task.Factory.StartNew(a1);
var t2 = t1.ContinueWith(a2, TaskContinuationOptions.NotOnFaulted);
var t3 = t2.ContinueWith(a3, TaskContinuationOptions.NotOnFaulted);

t3.Wait();

Now assume that a1 throws an exception. What happens is that t1 becomes faulted (t1.Status == TaskStatus.Faulted). Because of that, t2 can't run (because of NotOnFaulted) and so it will be canceled. But this is not what you presumably expected: t2 won't be faulted, it will be canceled (t2.Status == TaskStatus.Canceled). But this means that t3 can run normally, and if it doesn't throw, t3.Wait() won't throw any exception.

How to fix this? First, you probably shouldn't use TaskContinuationOptions.NotOnFaulted, but TaskContinuationOptions.OnlyOnRanToCompletion instead. But that doesn't solve the problem of “disappearing” exceptions. To solve that, I see two possibilities:

  1. Call Wait() at the beginning of each continuation and don't use any TaskContinuationOptions. This means you might get some exception wrapped in AggregateException, which is itself wrapped in AggregateException, which is wrapped in another AggregateException, etc. To solve this, you could use Flatten() or Handle().

  2. Wait for all the tasks, by using Task.WaitAll(). WaitAll() will throw an AggregateException that will contain the original exception and also TaskCanceledException for each of the tasks that were canceled because of the first exception.

Upvotes: 6

Related Questions