简体   繁体   中英

Fan-out computation in WebApi using async / await

I have say an interface called:

interface IExecutor {
   String Name { get; }
   Task<int> Execute();
}

And two implementations of it (details are irrelevant and you can assume methods are marked with async and work as expected). Each implementation takes between 2-3 seconds to run and there will ever be between 2 and 5 implementations of IExecutor

I have a controller that needs to run all executors and return the results in as ExecutorResult objects. Where ExecutorResult is:

class ExecutorResult {
    int Result; // assume usual get n set
    String ExecutorName;
}

Ideally this should happen in a Fan-Out approach.

I have thought of the following approaches:

List<Task<int>> tasks = new List<Task<int>>();
foreach(var executor in executors) {
    tasks.Add(executor.Execute());
}

var results = Task.WhenAll(tasks);

The problem I have with this approach is that I m not sure if this is best practice in an ASP WebAPI application. Also - given I would like to return Result objects - where Result needs an the name of the executor and the int result from Execute the above solution doesn't work as outside of the for loop I no longer have access to the Name property of each executor.

So what is the best practice for this approach (again - given a Web Api application and not a Console app)

What you have already is a best practice. What you're doing is asynchronous concurrency , which is best done with Task.WhenAll .

Note that the code:

List<Task<int>> tasks = new List<Task<int>>();
foreach(var executor in executors) {
  tasks.Add(executor.Execute());
}
var results = Task.WhenAll(tasks);

can be simplified to:

var results = Task.WhenAll(executors.Select(e => e.Execute()));

In spite of the countless examples using List<Task> , you don't actually have to build one up explicitly.

If I understood it correctly, you're looking for somethng like this:

var tasks = new List<Task<ExecutorResult>>();
foreach (var executor in executors)
{
    tasks.Add(((Func<IExecutor, Task<ExecutorResult>>)(
        async (e) => new ExecutorResult
        {
            ExecutorName = e.Name,
            Result = await e.Execute()
        }))(executor));
}

var results = Task.WhenAll(tasks);

OR, following Stephen Clearly suggestion:

var results = Task.WhenAll(
    from executor in executors
    select ((Func<IExecutor, Task<ExecutorResult>>)(
        async (e) => new ExecutorResult
        {
            ExecutorName = e.Name,
            Result = await e.Execute()
        }))(executor))
    );

This worked for me:

public class Executor : IExecutor
{
   public String Name { get; set;}

   public async Task<int> Execute()
   {
      Console.WriteLine("Executing " + Name);
      await Task.Delay(3000);
      Console.WriteLine("Finished Executing " + Name);
      return 0;
   }
}
public async Task<ExecutorResult> Execute(IExecutor executor)
{
    return new ExecutorResult { ExecutorName = executor.Name,
                                Result = await executor.Execute() };
}

public async Task MainAsync()
{
    var executors = new List<IExecutor>
    {
        new Executor { Name = "Executor1" },
        new Executor { Name = "Executor2" },
        new Executor { Name = "Executor3" }
    };

    List<Task<ExecutorResult>> tasks = new List<Task<ExecutorResult>>();

    foreach(var executor in executors)
    {
        tasks.Add(Execute(executor));
    }

    var results = await Task.WhenAll(tasks);
}

void Main()
{
    MainAsync().Wait();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM