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);
Let’s simplify your code a bit:
Now assume that
a1throws an exception. What happens is thatt1becomes faulted (t1.Status == TaskStatus.Faulted). Because of that,t2can’t run (because ofNotOnFaulted) and so it will be canceled. But this is not what you presumably expected:t2won’t be faulted, it will be canceled (t2.Status == TaskStatus.Canceled). But this means thatt3can 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, butTaskContinuationOptions.OnlyOnRanToCompletioninstead. But that doesn’t solve the problem of “disappearing” exceptions. To solve that, I see two possibilities:Call
Wait()at the beginning of each continuation and don’t use anyTaskContinuationOptions. This means you might get some exception wrapped inAggregateException, which is itself wrapped inAggregateException, which is wrapped in anotherAggregateException, etc. To solve this, you could useFlatten()orHandle().Wait for all the tasks, by using
Task.WaitAll().WaitAll()will throw anAggregateExceptionthat will contain the original exception and alsoTaskCanceledExceptionfor each of the tasks that were canceled because of the first exception.