Reputation: 551
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
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:
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()
.
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