How to put async method in a list and invoke them iteratively?

Viewed 1216

Recently I want to implement a health check for a list of service calls. They are all async task (e.g. Task<IHttpOperationResponse<XXX_Model>> method_name(...)

I would like to put all of them into a list. I followed the answer of this post: Storing a list of methods in C# However, they are async methods.

I put it like this: a collection of async method

List<Action> _functions = new List<Action> {
      () => accountDetailsServiceProvider.GetEmployer(EmployerId),                                  
      () => accountServiceProvider.GetAccountStatus(EmployerId)
}

Can someone direct me to the right way to implement putting async methods in to a list and invoke them iteratively?

Thanks in advance!

3 Answers

First, you need to make your methods async. That means they must return a Task. For example:

public static async Task Foo()
{
    await Task.Delay(1);
    Console.WriteLine("Foo!");
}

public static async Task Bar()
{
    await Task.Delay(1);
    Console.WriteLine("Bar!");
}

Then to put them in a list, you must define the list as containing the right type. Since an async method actually returns something, it's a Func, not an action. It returns a Task.

var actions = new List<Func<Task>>
{
    Foo, Bar
};

To invoke them, Select over the list (using Linq) to invoke them. This creates a list of Tasks in place of the list of Funcs.

var tasks = actions.Select( x => x() );

Then just await them:

await Task.WhenAll(tasks);

Full example:

public static async Task MainAsync()
{
    var actions = new List<Func<Task>>
    {
        Foo, Bar
    };
    var tasks = actions.Select( x => x() );
    await Task.WhenAll(tasks);
}

Output:

Foo!
Bar!

Example on DotNetFiddle

If your methods return a Boolean value, then the return type becomes Task<bool> and the rest follows suit:

public static async Task<bool> Foo()
{
    await Task.Delay(1);
    Console.WriteLine("Foo!");
    return true;
}

public static async Task<bool> Bar()
{
    await Task.Delay(1);
    Console.WriteLine("Bar!");
    return true;
}

var actions = new List<Func<Task<bool>>>
{
    Foo, Bar
};

var tasks = actions.Select( x => x() );

await Task.WhenAll(tasks);

After you have awaited them, you can convert the tasks to their results with one more LINQ statement:

List<bool> results = tasks.Select( task => task.Result ).ToList();

I think you are just looking for something simple like this?

var myList = new List<Action>()
{
    async() => { await Foo.GetBarAsync(); },
    ...
};

I would recommend you to change the type from Action to Func<Task> like so instead.

var myList = new List<Func<Task>>()
{
  async() => { await Foo.GetBarAsync(); },
};

You can read more about why here: https://blogs.msdn.microsoft.com/pfxteam/2012/02/08/potential-pitfalls-to-avoid-when-passing-around-async-lambdas/

To invoke (simplified)

foreach (var action in myList)
{
    await action.Invoke();
}

Based on the comments:

However, my task requires a boolean value for each method call, because I have to report the status to the frontend whether the service is down or not

Create a wrapper method for the method which will return required boolean value

public async Task<Result> Check(string name, Func<Task> execute)
{
    try
    {
        await execute();
        return new Result(name, true, string.Empty);
    }
    catch (Exception ex)
    {
        return new Result(name, false, ex.Message);
    }
}

public class Result
{
    public string Name { get; }
    public bool Success { get; }
    public string Message { get; }
    public Result(string name, bool success, string message) 
        => (Name, Success, Message) = (name, success, message);
}

Then you don't need to have collection of delegates, instead you will have collection of Task.

var tasks = new[]
{
    Check(nameof(details.GetEmployer), () => details.GetEmployer(Id)),
    Check(nameof(accounts.GetAccountStatus), () => accounts.GetAccountStatus(Id)),
};

var completed = await Task.WhenAll(tasks);

foreach (var task in completed)
{
    Console.WriteLine($"Task: {task.Name}, Success: {task.Success};");
}
Related