3

I have these two scenarios, but I don't understand why things are happening as they do:

    static void Main(string[] args)
    {
        Console.WriteLine("***Starting T1");
        //run two tasks sequentially
        Task t = FirstTask().ContinueWith(_ => SecondTask(), TaskContinuationOptions.OnlyOnRanToCompletion);
        //register succeded and faulted continuations
        t.ContinueWith(_ => Completion(), TaskContinuationOptions.OnlyOnRanToCompletion);
        t.ContinueWith(_ => Faulted(), TaskContinuationOptions.OnlyOnFaulted);
        Console.ReadLine();
        Console.WriteLine("***Starting T2");
        Task t2 = FirstTask().ContinueWith(_ => FaultTask(), TaskContinuationOptions.OnlyOnRanToCompletion);
        t2.ContinueWith(_ => Completion(), TaskContinuationOptions.OnlyOnRanToCompletion);
        t2.ContinueWith(_ => Faulted(), TaskContinuationOptions.OnlyOnFaulted);
        Console.ReadLine();
        Console.WriteLine("***Starting T3");
        Task t3 = FirstTask().ContinueWith(ant => ant.ContinueWith(_ => FaultTask(), TaskContinuationOptions.OnlyOnRanToCompletion));
        t3.ContinueWith(_ => Completion(), TaskContinuationOptions.OnlyOnRanToCompletion);
        t3.ContinueWith(_ => Faulted(), TaskContinuationOptions.OnlyOnFaulted);
        Console.ReadLine();
    }

    private static Task FirstTask()
    {
        return Task.Run(() =>
        {
            Console.WriteLine("Task 1");
            Thread.Sleep(1000);
        });
    }

    private static Task SecondTask()
    {
        return Task.Run(() =>
        {
            Console.WriteLine("Task 2");
            Thread.Sleep(1000);
        });
    }

    private static Task FaultTask()
    {
        return Task.Run(() =>
        {
            Console.WriteLine("Throw...");
            Thread.Sleep(1000);
            throw new ArgumentException();
        });
    }

    private static void Completion()
    {
        Console.WriteLine("Complete");
    }

    private static void Faulted()
    {
        Console.WriteLine("Faulted");
    }

In case 1, things are ran as expected. However, if the Sleep() in FirstTask() is removed, there is no guarantee the tasks are run in order.

In case 2, The Faulted() handler is not run. I would expect this to occur since there is an unhandled exception.

In case 3, the exception is thrown after the Complete() handler is run. I am confused as to why this ordering has occurred.

Basically I want to be able to chain as many tasks as I want and for them to run in order after the previous one finished. Once I have created the chain, I will then show a wait form and register continuations for OnlyOnRanToCompletion, OnlyOnCancelled, OnlyOnFaulted into the final task (read: after all have finished) to close the form - displaying success or error.

Is this where MSDN is referring to those options that are not available for multi task continuations?

Nate-Wilkins
  • 5,364
  • 4
  • 46
  • 61
Simon
  • 9,197
  • 13
  • 72
  • 115
  • 3
    Both tasks [ran to completion](https://dotnetfiddle.net/dul82Y). Voting to close as can't reproduce. Will retract once you update the question with code which can reproduce the problem. I suspect your original code will have some Continuation flags set which you're not showing us. – Sriram Sakthivel Dec 16 '14 at 09:49
  • Still your code won't show the described behavior. Please run the code and test yourself before posting it. Thanks. – Sriram Sakthivel Dec 16 '14 at 10:10
  • Can you please post the code required to demonstrate the issue such that I can copy-and-paste the code into a console app and it will run? Currently the first task won't even compile. – Enigmativity Dec 16 '14 at 10:29
  • @Simon, whenever you call `Task.Run()`, you task is already started. So, for example, in the first line of code you're starting both tasks simultaneously – Zruty Dec 17 '14 at 02:37

1 Answers1

2

Your return type on t and t2 are Task<Task> not just a Task. T3 is a Task<Task<Task>>. To get the desired behavior, you should be able to Unwrap the tasks which will give you a task that represents the entire operation (read the docs for more info):

Console.WriteLine("***Starting T1");
//run two tasks sequentially
Task<Task> t = FirstTask().ContinueWith(_ => SecondTask(), TaskContinuationOptions.OnlyOnRanToCompletion);

//register succeded and faulted continuations
t.Unwrap().ContinueWith(_ => Completion(), TaskContinuationOptions.OnlyOnRanToCompletion);
t.Unwrap().ContinueWith(_ => Faulted(), TaskContinuationOptions.OnlyOnFaulted);

Console.ReadLine();
Console.WriteLine("***Starting T2");

Task<Task> t2 = FirstTask().ContinueWith(_ => FaultTask(), TaskContinuationOptions.OnlyOnRanToCompletion);
t2.Unwrap().ContinueWith(_ => Completion(), TaskContinuationOptions.OnlyOnRanToCompletion);
t2.Unwrap().ContinueWith(_ => Faulted(), TaskContinuationOptions.OnlyOnFaulted);
Console.ReadLine();

I would recommend looking into using the async/await pattern where possible, as it makes dealing with tasks like these much easier.

John Koerner
  • 37,428
  • 8
  • 84
  • 134
  • thanks, yes i understand normally I can use: `await t1; await t2; await t3;`, but then checking task completion status is a little more cumbersome – Simon Dec 17 '14 at 04:15
  • @Simon, Why do you consider it more cumbersome? It is certainly more readable. Most likely, whatever you are considering more cumbersome with the async/await approach could be fixed by a helper method or two. – Matt Smith Dec 17 '14 at 13:48
  • The solution to only perform `t2` if the first ran successfully, or only run `t3` if the first faulted is IMO less readable. you have to hold a ref to the `Task` and then perform an `if` – Simon Dec 17 '14 at 13:53