1

I am using the following code in a .NET 6 console application.

using .....
List<Location> locations=GetLocations();
locations.ForEach(async l => await UpdateConfigAsync(l)); //Does Not Work
//foreach (Location l in locations) Task.WaitAll(UpdateConfig(l)); //Works fine

async Task UpdateConfigAsync(Location l)
{
    await ConfigToDbAsync("storeProc1", HttpMethod.Get, "url1", l);
    await ConfigToDbAsync("storeProc2", HttpMethod.Get, "url2", l);
    await ConfigToDbAsync("storeProc3", HttpMethod.Get, "url3", l);
    .....
}
async Task ConfigToDbAsync(string storeProc, HttpMethod method, string apiReq,
    Location l)
{
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
       conn.Open();
       using (HttpRequestMessage request = new HttpRequestMessage(method, apiReq))
            using (HttpResponseMessage response = await R365httpClient.SendAsync(request))
             {
                string json = await response.Content.ReadAsStringAsync();
                if (response.IsSuccessStatusCode)
                {
                   using (SqlCommand command = new SqlCommand(storeProc, conn))
                   {
                      command.CommandTimeout = 300;
                      //Command Parameters...
                      await command.ExecuteNonQueryAsync();
                   }
                }
                response.EnsureSuccessStatusCode();
                }
            }
        }
    }
}

But it seems that locations.ForEach does not wait for the task UpdateConfigAsync (Nothing is sending to the database) and the program ends with no error.

Is there any wrong with the code?

Note: foreach (Location l in locations) Task.WaitAll(UpdateConfig(l)); works fine.

(I don't speak English)

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104

1 Answers1

4

The problem comes, because you don't (or better can't) await the result from the .ForEach() call, cause this overload just takes an Action and not a Func<Task>. Better would be:

var updateTasks = locations.Select(l => UpdateConfigAsync(l));
var results = await Task.WhenAll(updateTasks);

In that case you only create a lazy enumeration of tasks that should run. The next step is then to await all of these tasks by using the correct method (which would be Task.WhenAll() in this case).

Oliver
  • 43,366
  • 8
  • 94
  • 151
  • can't - because [List.ForEach](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.foreach?view=net-6.0) takes an `Action` as a parameter. You're returning a `Task` from each `UpdateConfigAsync` called inside the ForEach, which then just gets dropped on the floor – Jonesopolis Mar 21 '22 at 18:54
  • 1
    IMHO the method `List.ForEach()` had never become official in .Net. This is a design flaw and just leads to such errors. Whenever you need to produce a side-effect on all items of a list, write a `foreach` loop. Makes it much clearer to the reader, that here is happening something *important*. – Oliver Mar 21 '22 at 18:57