3

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!

Terence
  • 652
  • 11
  • 34
  • Also please dont paste images of code. – TheGeneral Jan 16 '19 at 00:54
  • This seems odd since the method names seem to indicate that they return a value but you're not doing anything with them. – juharr Jan 16 '19 at 01:08
  • If `XXX_Model` in `Task>` mean different types, you will not be able to observe returned responses. Because collection of methods should have same signature. – Fabio Jan 16 '19 at 01:11
  • Please explain exactly what you are trying to do, as this seems XYish for now, past your code and not images, and any other information including signatures, its unclear what your asking – TheGeneral Jan 16 '19 at 01:47
  • sorry guys, I changed it to code now. – Terence Jan 16 '19 at 02:16
  • Do you want to execute them in order? do you want to execute them at the same time, what are your signatures to these methods, there is lots of information missing here – TheGeneral Jan 16 '19 at 02:18
  • @TheGeneral Yes, these methods have different return type. They are backend service calls. what I want to do is to verify if they can be executed successfully. I'll use a try catch block to capture if something is wrong for particular method call. – Terence Jan 16 '19 at 02:21

3 Answers3

5

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();
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • I really like this Linq solution. – Terence Jan 16 '19 at 02:18
  • What if I want to check the method individually? Istead of using `WhenAll`, what should I use? – Terence Jan 16 '19 at 02:30
  • @Terence, you will not be able to check result of your methods, only fact does method complete or not. – Fabio Jan 16 '19 at 05:45
  • You can of course await the tasks separately if you want (using the `await` keyword) or if you just want to check to see if it is completed without waiting you can check the [IsCompleted](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.iscompleted?view=netframework-4.7.2) property of the task. – John Wu Jan 16 '19 at 06:25
  • @Fabio You are right. I just want to check if the methods are executed successfully. – Terence Jan 16 '19 at 09:05
  • @Terence, After `await Task.WhenAll(tasks)` all tasks will be completed successfully. In case one of the tasks fails exception will be thrown. – Fabio Jan 16 '19 at 09:07
  • @Fabio Yes, I think that would do the job. 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. – Terence Jan 16 '19 at 09:11
2

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();
}
Svek
  • 12,350
  • 6
  • 38
  • 69
  • Thank you for your answer. One more question: Is there any way I can invoke each method iteratively? – Terence Jan 16 '19 at 02:26
  • Should be noted that this results in fire and forget. Basically you will not know when any one of those methods completes. – juharr Jan 16 '19 at 04:29
  • I wonder why the downvote? Maybe someone could explain how this does not answer the OP's question? – Svek Jan 16 '19 at 05:57
  • @Svek I think your answer is simple and nice. No idea where the downvote come from. – Terence Jan 16 '19 at 09:15
1

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};");
}
Fabio
  • 31,528
  • 4
  • 33
  • 72