3
votes

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?

1
Both tasks ran to completion. 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
Still your code won't show the described behavior. Please run the code and test yourself before posting it. Thanks. - Sriram Sakthivel
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
@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
Ah yes, thanks Zruty - Simon

1 Answers

2
votes

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.