I'm a little confused about how to use Tasks with conditional Continuations.
If I have a task, and then I want to continue with a tasks that handle success and error, and then wait on those to complete.
void FunctionThrows() {throw new Exception("faulted");}
static void MyTest()
{
var taskThrows = Task.Factory.StartNew(() => FunctionThrows());
var onSuccess = taskThrows.ContinueWith(
prev => Console.WriteLine("success"),
TaskContinuationOptions.OnlyOnRanToCompleted);
var onError = taskThrows.ContinueWith(
prev => Console.WriteLine(prev.Exception),
TaskContinuationOptions.OnlyOnFaulted);
//so far, so good
//this throws because onSuccess was cancelled before it was started
Task.WaitAll(onSuccess, onError);
}
Is this the preferred way of doing task success/failure branching? Also, how am I supposed to join all these tasks, suppose I've created a long line of continuations, each having their own error handling.
//for example
var task1 = Task.Factory.StartNew(() => ...)
var task1Error = task1.ContinueWith( //on faulted
var task2 = task1.ContinueWith( //on success
var task2Error = task2.ContinueWith( //on faulted
var task3 = task2.ContinueWith( //on success
//etc
Calling WaitAll
on these invariably throws, because some of the continuations will be cancelled due to the TaskContinuationOptions
, and calling Wait
on a cancelled task throws.
How do I join these without getting the "A task was cancelled" exception"?
I think your main problem is that you're telling those two tasks to "Wait" with your call to
Task.WaitAll(onSuccess, onError);
The onSuccess and onError continuations are automatically setup for you and will be executed after their antecedent task completes.
If you simply replace your Task.WaitAll(...)
with taskThrows.Start();
I believe you will get the desired output.
Here is a bit of an example I put together:
class Program
{
static int DivideBy(int divisor)
{
Thread.Sleep(2000);
return 10 / divisor;
}
static void Main(string[] args)
{
const int value = 0;
var exceptionTask = new Task<int>(() => DivideBy(value));
exceptionTask.ContinueWith(result => Console.WriteLine("Faulted ..."), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
exceptionTask.ContinueWith(result => Console.WriteLine("Success ..."), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
exceptionTask.Start();
try
{
exceptionTask.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine("Exception: {0}", ex.InnerException.Message);
}
Console.WriteLine("Press <Enter> to continue ...");
Console.ReadLine();
}
}
Use Task.WaitAny(onSuccess, onError);
Isn't that normal?
Looking at the MSDN documentation you're doing it fine and the logic you're implementing is sound. The only thing you're missing is wrapping the WaitAll call in an AggregateException wrapper like so:
// Exceptions thrown by tasks will be propagated to the main thread
// while it waits for the tasks. The actual exceptions will be wrapped in AggregateException.
try
{
// Wait for all the tasks to finish.
Task.WaitAll(tasks);
// We should never get to this point
Console.WriteLine("WaitAll() has not thrown exceptions. THIS WAS NOT EXPECTED.");
}
catch (AggregateException e)
{
Console.WriteLine("\nThe following exceptions have been thrown by WaitAll(): (THIS WAS EXPECTED)");
for (int j = 0; j < e.InnerExceptions.Count; j++)
{
Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString());
}
}
You can read more here:
http://msdn.microsoft.com/en-us/library/dd270695.aspx
In essence catching an AggregatedException gets you the same thing as completing WaitAll. It's a collection of all the exceptions returned from your tasks.