JT Smith
JT Smith

Reputation: 381

Chaining async methods

I'm attempting to chain together a few async methods I've created and I believe there is some fundamental misunderstanding on my part about how this works

Here's a representation of my code:

public async Task<bool> LoadFoo()
{
    return await Foo.ReadAsync("bar").ContinueWith((bar) =>
    {
        Foo.ReadAsync("baz").ContinueWith((baz) =>
        {
            Foo.ReadAsync("qux").ContinueWith((qux) =>
            {
                return true;
            });

            return true;
        });

        return true;
    });
}

public void LoadEverything()
{
    LoadFoo().ContinueWith((blah) =>
    {
        OtherLoadMethod();
    });
}

Now I was expecting when LoadEverything() was called that all of the ReadAsync methods in LoadFoo ("bar", "baz" and "qux") would run and complete, and after they all completed then the .ContinueWith in LoadEverything would run so that OtherLoadMethod() wouldn't execute until the "bar", "baz" and "qux" ReadAsync methods finished.

What I am actually seeing is that LoadFoo gets called and then OtherLoadMethod starts to run before getting to the final completion in LoadFoo (the ContinueWith of the "qux" ReadAsync).

Can someone help clear up my misunderstanding here? Why wouldn't the execution of OtherLoadMethod wait until ReadAsync("qux") finishes and returns true?

Upvotes: 4

Views: 12389

Answers (3)

Wayne Wang - MSFT
Wayne Wang - MSFT

Reputation: 82

Did not expect the top solution in search engine was fixing a misunderstanding but not actually the solution of the topic itself.

Chaining call can make great context focus when developing, async is good, and it is better if you know what u r doing.

Code:

    //thanks for TheodorZoulias's review and input
public static class TaskHelper
{
    public static async Task<TOutput> ThenAsync<TInput, TOutput>(
        this Task<TInput> inputTask,
        Func<TInput, Task<TOutput>> continuationFunction,
        bool continueOnCapturedContext= true)
    {
        var input = await inputTask.ConfigureAwait(continueOnCapturedContext);
        var output = await continuationFunction(input).ConfigureAwait(continueOnCapturedContext);
        return output;
    }
    public static async Task<TOutput> ThenAsync<TInput, TOutput>(
        this Task<TInput> inputTask,
        Func<TInput, TOutput> continuationFunction,
        bool continueOnCapturedContext= true)
    {
        var input = await inputTask.ConfigureAwait(continueOnCapturedContext);
        var output = continuationFunction(input);
        return output;

    }

    public static async Task<TInput> ThenAsync<TInput>(
        this Task<TInput> inputTask,
        Action<TInput> continuationFunction,
        bool continueOnCapturedContext= true)
    {
        var input = await inputTask.ConfigureAwait(continueOnCapturedContext);
        continuationFunction(input);
        return input;
    }

    public static async Task<TInput> ThenAsync<TInput>(
        this Task<TInput> inputTask,
        Func<TInput, Task> continuationFunction,
        bool continueOnCapturedContext= true)
    {
        var input = await inputTask.ConfigureAwait(continueOnCapturedContext);
        await continuationFunction(input).ConfigureAwait(continueOnCapturedContext);
        return input;
    }




    public static async Task<TOutput> ThenAsync<TOutput>(
       this Task inputTask,
       Func<Task<TOutput>> continuationFunction,
       bool continueOnCapturedContext= true)
    {
        await inputTask.ConfigureAwait(continueOnCapturedContext);
        var output = await continuationFunction().ConfigureAwait(continueOnCapturedContext);
        return output;
    }

    public static async Task ThenAsync(
        this Task inputTask,
        Action continuationFunction,
        bool continueOnCapturedContext= true)
    {
        await inputTask.ConfigureAwait(continueOnCapturedContext);
        continuationFunction();
    }
}

Upvotes: -1

Fabian
Fabian

Reputation: 1190

You can also use Unwrap:

public async Task<bool> LoadFoo()
{
    await Foo.ReadAsync("bar")
        .ContinueWith(_ => Foo.ReadAsync("baz")).Unwrap()
        .ContinueWith(_ => Foo.ReadAsync("qux")).Unwrap();
    return true;
}

public async Task LoadEverything()
{
    await LoadFoo().ContinueWith(_ => OtherLoadMethod()).Unwrap();
}

Upvotes: -1

Peter Duniho
Peter Duniho

Reputation: 70652

Why wouldn't execution of OtherLoadMethod wait until ReadAsync("qux") finishes and returns true?

Because that's how await works. The continuations you register are just that: continuations. They are not executed synchronously in the current method. You are telling the framework that when the current task completes, the continuation should be executed. The Task object returned by ContinueWith() allows you to observe the completion if and when it happens. There would be no need to even return a Task object, if the ContinueWith() method blocked until the continuation was executed.

Likewise, the Task<bool> returned by your LoadFoo() method represents the overall completion of the method, including the await...ContinueWith() that you're returning. The method returns prior to completion of the continuation, and callers are expected to use the returned task if they need to wait for the continuation to complete.

All that said, I don't understand why you're using ContinueWith() in the first place. You obviously have access to await, which is the modern, idiomatic way to handle continuations. IMHO, your code should look something like this (it's not clear why you're returning Task<bool> instead of Task, since the return value is only ever true, but I assume you can figure that part out yourself):

public async Task<bool> LoadFoo()
{
    await Foo.ReadAsync("bar");
    await Foo.ReadAsync("baz");
    await Foo.ReadAsync("qux");

    return true;
}

public async Task LoadEverything()
{
    await LoadFoo();
    await OtherLoadMethod();
}

Upvotes: 13

Related Questions