0

I recently learned about Task.ContinueWith and am trying to apply it to some existing code I have for better performance. I have something like this:

        public static async Task<TResult> DoWork<T, TResult>(T value)
        {
            var fooResult = await Foo(value);
            return await Bar<TResult>(fooResult);
        }

        private static Task<MyClass> Foo<T>(T value)
        {
            return GetMyClassFromValueAsync(value);
        }

        private static Task<TResult> Bar<TResult>(MyClass myClass)
        {
            return myClass.DoOperationAsync();
        }

and was thinking I could improve it by doing something like this instead:

        public static Task<TResult> DoWork<T, TResult>(T value)
        {
            return Foo(value).ContinueWith(fooResult => Bar<TResult>(fooResult.Result));
        }

The problem that I'm running into is that ContinueWith returns a type of Task<Task<TResult>> in this case because it wraps my async call in another task. The only solution I've found is to do something like this:

        public static async Task<TResult> DoWork<T, TResult>(T value)
        {
            return await await Foo(value).ContinueWith(fooResult => Bar<TResult>(fooResult.Result));
        }

but that just looks wrong and forces me to await it. Is there a better way to handle this situation?

There's also a lot happening behind the scenes with these calls, so if I'm taking the wrong approach or my intuition is leading me astray and I should stick with my original approach, please let me know why so I can learn and improve. Thanks!

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • 3
    You should probably avoid continuations and simply use `await`. _["They're very similar in that they both schedule a continuation, but as soon as the control flow gets even slightly complex, await leads to much simpler code. Additionally, as noted by Servy in comments, awaiting a task will "unwrap" aggregate exceptions which usually leads to simpler error handling"](https://stackoverflow.com/a/18965299/585968)_ - **Jon Skeet** –  May 21 '20 at 23:25
  • 1
    Your first `DoWork` with `await`s and your second `DoWork` with `ContinueWith` are for all real world purposes exactly the same, except that the second one is harder to read. – JohanP May 22 '20 at 00:09
  • using the await keyword is already roughly equivalent to `.GetAwaiter().OnCompleted( .... )`. You won't save anything by trying to reimplement that. – Jeremy Lakeman May 22 '20 at 05:29

3 Answers3

1

You are very close. You are missing the UnWrap method at the end.

public static async Task<TResult> DoWork<T, TResult>(T value)
        {
            return await await Foo(value).ContinueWith(fooResult => Bar<TResult>(fooResult.Result)).UnWrap();
        }

In reality though, what you are doing with ContinueWith is no more efficient than what you had before with the await. I believe the await is just syntactic sugar for ContinueWith. I personally find await much easier to read.

peinearydevelopment
  • 11,042
  • 5
  • 48
  • 76
1

You are looking to chain together promises and return the final result, there is an extension method Unwrap for this.

Example:

class Program
{
    static void Main(string[] args)
    {

        var finalWork = Work().ContinueWith(work => MoreWork(work.Result)).Unwrap();
        finalWork.Wait();
        var finalWorkResult = finalWork.Result;
        Console.WriteLine(finalWorkResult);

    }

    private static Task<string> MoreWork(string s)
    {
        return Task.FromResult($"{s} done... now more work.");
    }

    private static Task<string> Work()
    {
        return Task.FromResult("Work work");
    }
}
arynaq
  • 6,710
  • 9
  • 44
  • 74
1

Using ContinueWiht can be problematic and it's unnecessarily complicated.

Just embrace async-await:

public static async Task<TResult> DoWork<T, TResult>(T value)
{
    return await Bar<TResult>(await Foo(value));
}
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59