Search code examples
c#async-awaittaskmethod-chaining

c#: Chain asynchronous methods


I've found a piece of code showing how to chain asynchronous methods. Here is the code:

    public static class ExtensionMethod
    {
        public static async Task<TResult> MapAsync<TSource, TResult>(
            this Task<TSource> @this,
            Func<TSource, Task<TResult>> fn)
        {
            return await fn(await @this);
        }
    }

    public partial class Program
    {
        public async static Task<int> FunctionA
            (int a) => await Task.FromResult(a * 1);

        public async static Task<int> FunctionB(
            int b) => await Task.FromResult(b * 2);

        public async static Task<int> FunctionC(
            int c) => await Task.FromResult(c * 3);

        public async static void AsyncChain()
        {
            int i = await FunctionC(10)
                .MapAsync(FunctionB)
                .MapAsync(FunctionA);

            Console.WriteLine("The result = {0}", i);
        }
    }

But I don't understand why inside the extension method MapAsync we have await fn(await @this)?

Can someone explain me this, please?


Solution

  • The reason for this is because in order to get the value of the previous call (in this case the value stored within the Task @this) it must be awaited. Because await is now being used in the method and it must be marked as async, you can now no longer simply return the result from fn because it's a Task. (As I'm sure you are aware, and in very simple terms, marking the method as async means that the method signature sort of ignores the Task and just wants an instance of the generic type as the return or nothing if it's not using the genric version of Task.)

    In response to your comment about chaining both sync vs async methods, this approach of using Task would still work for sync methods, you just have to pretend that its async by wrapping the result in a task (which is already marked as completed). Something like this:

    public async static Task<int> AsyncFunction(int x) 
        => await SomeAsyncMethodThatReturnsTaskOfInt();
    
    public static Task<int> SyncFunction(int x) 
        => Task.FromResult(SomeSyncMethodThatReturnsInt());
    
    public async static void AsyncChain()
    {
        int i = await AsyncFunction(10)
            .MapAsync(SyncFunction);
    
        Console.WriteLine("The result = {0}", i);
    }
    

    You can remove all the async/awaits from the FunctionA, FunctionB and FunctionC definitions as they can just return the task.

    There is a nice article on Eliding await by Stephen Cleary for a better explanation of when you should and shouldn't use await when the only thing the method is doing is returning Task.

    Having said all that, Gusman's comment is completely correct that this is total overkill for a fairly simple ContinueWith