Using Tasks with conditional continuations

2019-02-04 01:24发布

问题:

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

回答1:

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();
    }
}


回答2:

Use Task.WaitAny(onSuccess, onError);



回答3:

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.