0

I have refactored a Web API to rely on async/await in ASP.NET Core 3.1 and I have the following scenario: a statistics method is sequentially computing a list of indicators which are defined in a list.

readonly Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>> simpleItemActionMap =
    new Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>>();

private void InitSimpleStatisticFunctionsMap()
{
    simpleItemActionMap.Add(StatisticItemEnum.AllQuestionCount, GetAllQuestionCountApiModel);
    simpleItemActionMap.Add(StatisticItemEnum.AllAnswerCount, GetAllAnswerCountApiModel);
    simpleItemActionMap.Add(StatisticItemEnum.AverageAnswer, GetAverageAnswer);
    // other mappings here
}

private async Task<SimpleStatisticItemApiModel> GetAllQuestionCountApiModel()
{
    // await for database operation
}

private async Task<SimpleStatisticItemApiModel> GetAllAnswerCountApiModel()
{
    // await for database operation
}

private async Task<SimpleStatisticItemApiModel> GetAverageAnswer()
{
    // await for database operation
}

The code sequentially goes through each item and computes it and after the refactoring it is looking like this:

itemIds.ForEach(itemId =>
{
    var itemEnumValue = (StatisticItemEnum) itemId;
    if (simpleItemActionMap.ContainsKey(itemEnumValue))
    {
        var result = simpleItemActionMap[itemEnumValue]().Result;
        payload.SimpleStatisticItemModels.Add(result);
    }
}); 

I know that Task.Result might lead to deadlocks, but I could not find any other way to make this work.

Question: How to execute a dynamic list of async functions in a sequential way?

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164

2 Answers2

1

You should change the ForEach call to a regular foreach, and then you can use await:

foreach (var itemId in itemIds)
{
  var itemEnumValue = (StatisticItemEnum) itemId;
  if (simpleItemActionMap.ContainsKey(itemEnumValue))
  {
    var result = await simpleItemActionMap[itemEnumValue]();
    payload.SimpleStatisticItemModels.Add(result);
  }
}

Do not make the ForEach lambda async; that will result in an async void method, and you should avoid async void.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

I think you can do this:

itemIds.ForEach(async itemId =>
{
    var itemEnumValue = (StatisticItemEnum) itemId;
    if (simpleItemActionMap.ContainsKey(itemEnumValue))
    {
        var result = await simpleItemActionMap[itemEnumValue]();
        payload.SimpleStatisticItemModels.Add(result);
    }
}); 
Michal Diviš
  • 2,008
  • 12
  • 19