-1

I am trying to find account details from DB (GetAccountDetailAsync) for an array of accounts and would like to run in parallel to make it faster.

[HttpPost]
public async Task<IHttpActionResult> GetAccountsAsync(IEnumerable<int> accountIds)
{

    var resultAccounts = new List<AccountDetail>();

    var task = Task.Run(() =>
    {
        Parallel.ForEach(accountIds, new ParallelOptions
        {
            MaxDegreeOfParallelism = 5 
        }, async accountId =>
        {
            var response = await GetAccountDetailAsync(accountId).ConfigureAwait(false);
            resultAccounts.AddRange(response);

        });
    });

    task.Wait();

    return Ok(resultAccounts);

}

But instead of getting the result I am getting though I've got task.Wait. Not sure why task.Wait is not being blocked.

"An asynchronous module or handler completed while an asynchronous operation was still pending."

Bulu
  • 1,351
  • 3
  • 16
  • 37
  • Are you sure `task.Wait()` is not blocking execution until `task` completes its execution? – andresantacruz Aug 27 '19 at 13:29
  • Yeah that's what I have noticed – Bulu Aug 27 '19 at 13:30
  • 1
    Remove the `task` altogether (and `Task.Run` and `Wait`) and just call `Parallel.ForEach` directly. And remove the `async` inside the `Parallel.ForEach`. – mjwills Aug 27 '19 at 13:30
  • Yeah why are you even creating that task? – Liam Aug 27 '19 at 13:31
  • 2
    Note your code isn't safe though since `List` is not thread-safe - and you appear to be calling `AddRange` from multiple threads. You really should use `accountIds.AsParallel().StuffHere().ToList()`. – mjwills Aug 27 '19 at 13:31
  • If I will just run Parallel.Each it returns no account as while the Parallel is being invoked it goes to the next. – Bulu Aug 27 '19 at 13:32
  • 1
    Possible duplicate of [Nesting await in Parallel.ForEach](https://stackoverflow.com/questions/11564506/nesting-await-in-parallel-foreach) – Liam Aug 27 '19 at 13:33
  • `Parallel.ForEach` doesn't really support `async` calls you need to use [TPL dataflow](https://stackoverflow.com/a/11565317/542251) – Liam Aug 27 '19 at 13:35
  • Or use a [standard async `for` loop](https://stackoverflow.com/a/11565531/542251) depending on if you really, really need parallel or async processing. Either way this is a dupe – Liam Aug 27 '19 at 13:37
  • @mjwills I totally agree with you. The list is not thread safe. But how is the syntax as I am trying to return a list to the controller. I am lost here – Bulu Aug 27 '19 at 13:37
  • and here's a [thread safe list (type) data structure](https://stackoverflow.com/a/5874347/542251) – Liam Aug 27 '19 at 13:39
  • One of the below answers should get you started @Bulu. – mjwills Aug 27 '19 at 13:56

2 Answers2

2

Parallel.ForEach doesn't work with async actions, but you could start all tasks and then wait for them all to complete using Task.WhenAll:

[HttpPost]
public async Task<IHttpActionResult> GetAccountsAsync(IEnumerable<int> accountIds)
{
    Task<List<AccountDetail>>[] tasks = accountIds.Select(accountId => GetAccountDetailAsync(accountId)).ToArray();
    List<AccountDetail>[] results = await Task.WhenAll(tasks);
    return Ok(results.SelectMany(x => x).ToList());
}
mm8
  • 163,881
  • 10
  • 57
  • 88
0

Assuming you have or can easily get a method GetAccountDetail without the async part, this would be the easiest way:

[HttpPost]
public async Task<IHttpActionResult> GetAccountsAsync(IEnumerable<int> accountIds)
{
   var resultList = accountIds.AsParallel()
                              .WithDegreeOfParallelism(5)
                              .Select(GetAccountDetail)
                              .ToList();
   return Ok(resultList);
}
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • "Without the async part", you might as well use `Parallel.ForEach` without creating any tasks. – mm8 Aug 27 '19 at 13:53
  • 1
    @mm8 Yes, but then they need to figure out concurrency and locking the result container themselves. – nvoigt Aug 27 '19 at 13:54
  • 1
    True, the `List` should either be replaced by a `ConcurrentBag` or you need to synchronize the access to it. – mm8 Aug 27 '19 at 13:55