32

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"?

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
dan
  • 9,712
  • 6
  • 49
  • 62

3 Answers3

14

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();
    }
}
Dave New
  • 38,496
  • 59
  • 215
  • 394
Bryan Ray
  • 931
  • 10
  • 26
  • the point is I want to wait for the task, and whatever continuations to complete (let's say I want to start the tasks, do other stuff, and then join). Since I don't know if the antecedent task was successful or if it threw, I have no way of knowing which one to wait on. This seems like a design flaw. – dan May 04 '11 at 21:18
  • Interesting, but I did note that the ContinuesWith returns _another_ task instance. Your code creates 3 and runs 1, I'm slightly puzzled. – H H May 04 '11 at 22:21
  • @dan - I'm still not entirely sure what you're looking to "wait" on? My two tasks are waiting on their antecedent (exceptionTask) to complete before one of them will execute. In the code I provided above the two "waiting" tasks are the fault and the success. If the "exceptionTask" task fails, then one task will be run (Faulted). If the "exceptionTask" succeeds then the other will be run (Success). Ultimately, you have 3 tasks. The parent, and it's two "dependent" tasks ... – Bryan Ray May 05 '11 at 13:25
  • @Henk - Notice that the #ContinueWith() methods take an optional paramater, _TaskContinuationOptions_, at the end stating when they should be executed. – Bryan Ray May 05 '11 at 13:26
  • @bryan I get that the continuations don't start until the antecedent finishes, but I want to wait for the continuations to complete. Is there no way to do this? – dan May 05 '11 at 17:04
  • @dan - i see what you're saying. I've modified the above code and perhaps this is what you're looking for? Note the _AttachedToParent_ continuation option. – Bryan Ray May 05 '11 at 18:52
  • @dan no problem. did you end up getting it hashed out? – Bryan Ray Mar 30 '12 at 14:42
  • @bryan yeah--I made a simplified wrapper around Task with fewer bells and whistles so people on my team didn't have to learn all the complexities of the interface. With 30+ developers on my team, one careless mistake could easily go unnoticed and cause a catastrophe – dan Mar 30 '12 at 16:23
  • @dan - Glad to hear it. if you ever get around to publishing the wrapper. I'd love to take a look. – Bryan Ray Mar 30 '12 at 22:35
  • @bryan two questions: * Is exceptionTask.Start() necessary ? * Is TaskContinuationOptions.AttachedToParent necessary ? – Oguz Karadenizli Sep 13 '12 at 00:45
0

Use Task.WaitAny(onSuccess, onError);

Dejisys
  • 130
  • 1
  • 7
0

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.

Dave New
  • 38,496
  • 59
  • 215
  • 394
Khepri
  • 9,547
  • 5
  • 45
  • 61
  • my onError task handled the exception. The problem is the onSuccess task was cancelled before it started. When WaitAll calls Wait() on it, that causes it to throw a "a task has been cancelled" exception... this is the thing that is confusing. – dan Apr 11 '11 at 20:19