Q:

Correct way to link Tasks together when return values are needed at different times #2

I asked a question yesterday and, unfortunately, even with the answers provided, I'm still hitting up on stumbling blocks about how to do things correctly... My issue is that my code actually works, but I'm a complete novice at concurrency programming and it feels like I'm not programming the correct way and, most importantly, I'm afraid of developing bad habits.
To make up a simplistic example to elaborate on yesterday's question, suppose I had the following methods:
static Task<IEnumerable<MyClass>> Task1(CancellationToken ct)

static Task<IEnumerable<int>> Task2(CancellationToken ct, List<string> StringList)

static Task<IEnumerable<String>> Task3(CancellationToken ct)

static Task<IEnumerable<Double>> Task4(CancellationToken ct)

static Task Task5(CancellationToken ct, IEnumerable<int> Task2Info, IEnumerable<string> Task3Info, IEnumerable<double> Task4Info)

static Task Task6(CancellationToken ct, IEnumerable<int> Task2Info, IEnumerable<MyClass> Task1Info)

And the code I've written that utilizes them looks as follows:
static Task execute(CancellationToken ct)
{
    IEnumerable<MyClass> Task1Info = null;
    List<string> StringList = null;
    IEnumerable<int> Task2Info = null;
    IEnumerable<string> Task3Info = null;
    IEnumerable<double> Task4Info = null;

    var TaskN = Task.Run(() =>
                    {
                        Task1Info = Task1(ct).Result;
                    }
                    , ct)
                    .ContinueWith(res =>
                    {
                        StringList = Task1Info.Select(k=> k.StringVal).ToList();
                        Task2Info = Task2(ct, StringList).Result;
                    }
                    , ct);

    return Task.Run(() =>
    {
        return Task.WhenAll
            (
                TaskN,
                Task.Run(() => { Task3Info = Task3(ct).Result; }, ct),
                Task.Run(() => { Task4Info = Task4(ct).Result; }, ct)
            )
            .ContinueWith(res =>
            {
                Task5(ct, Task2Info, Task3Info, Task4Info).Wait();
            }
            , ct)
            .ContinueWith(res =>
            {
                Task6(ct, Task2Info, Task1Info).Wait();
            }
            , ct);
    });
}

In other words:

I need the results of Task1 to calculate StringList and to run Task2
Task2, Task3 and Task4 can all run concurrently 
I need the return values from all of the above for later method calls
Once these are run, I use their results to run Task5
Once Task5 is run, I use all the results in running Task6

As a simple explanation, imagine the first portion is data gathering, the second is data cleansing and the third data reporting
Like I said, my challenge is that this actually runs, but I simply feel that it's more of a "hack" that the right way to program - Concurrency programming is very new to me and I definitely want to learn the best ways this should be done...

A:

I would feel better about my answer if I could see how your TaskN methods were implemented. Specifically, I would want to validate the need for your TaskN method calls to be wrapped inside calls to Task.Run() if they are already returning a Task return value.
But personally, I would just use the async-await style of programming. I find it fairly easy to read/write. Documentation can be found here.
I would then envision your code looking something like this:
static async Task execute(CancellationToken ct)
{
    // execute and wait for task1 to complete.
    IEnumerable<MyClass> Task1Info = await Task1(ct);

    List<string> StringList = Task1Info.Select(k=> k.StringVal).ToList();

    // start tasks 2 through 4
    Task<IEnumerable<int>> t2 = Task2(ct, StringList);
    Task<IEnumerable<string>> t3 = Task3(ct);
    Task<IEnmerable<Double>> t4 = Task4(ct);

    // now that tasks 2 to 4 have been started,
    // wait for all 3 of them to complete before continuing.
    IEnumerable<int> Task2Info = await t2;
    IEnumerable<string> Task3Info = await t3;
    IEnumerable<Double> Task4Info = await t4;

    // execute and wait for task 5 to complete
    await Task5(ct, Task2Info, Task3Info, Task4Info);

    // finally, execute and wait for task 6 to complete
    await Task6(ct, Task2Info, Task1Info);
}

