Search code examples
c#.netsql-serverasync-awaitparallel.for

Async-await overuse


I've got a question about an overuse of async-await operators.
I have a list of Zoo instances and I need to insert each member of each of Zoo into a database separately. Here is the Zoo class definition:

class Zoo
{
public Dog Dog { get; set; }
public Cat Cat { get; set; }
public Bear Bear { get; set; }
}

The method to insert Zoo is provided below. Each instance of Zoo and its members should be validated or transformed before insertion, so I deal with them with Parallel.For:

public void Save(List<Zoo> zoos)
{
 Parallel.For(0, zoos.Count, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async i =>
  {
  ...
  Task saveDog = Task.Run(async () => await InsertDogAsync(zoos[i], string connectionString));
  Task saveCat = Task.Run(async () => await InsertCatAsync(zoos[i], string connectionString));
  Task saveBear = Task.Run(async () => await InsertBearAsync(zoos[i], string connectionString));
  await Task.WhenAll(new Task[] { saveDog, saveCat, saveBear });
  ...
  });
}

I insert animals to the database using async methods. For example here it is for Dog:

private static async Task InsertDogAsync(Zoo zoo, string connectionString)
{
 using SqlConnection connection = new SqlConnection(connectionString);
 await connection.OpenAsync();
 ...
 using SqlCommand insertCommand = new SqlCommand("sp_insertDog", connection); // stored procedure
 ...
 await insertCommand.ExecuteNonQueryAsync();
}

My question is following: is there an overuse of async-await operators? As I realized, each await operator releases thread before it will be completed, but methods are called in a parallel task, so threads are used in a different tasks. Maybe it is more acceptable to remove some async-await, for example, from Task.Run lambda?


Solution

  • This is not a case of await overuse, it's a case of await misuse. The Parallel.ForEach method is not async-friendly, and feeding it with an async delegate is a bug: it doesn't do what you expect it to do. If you want to launch multiple asynchronous operation concurrently, while keeping the level of concurrency under control, the correct API to use is the Parallel.ForEachAsync method. This method will be introduced with the upcoming .NET 6, so for now you can utilize only home-made solutions, like the ones found in this question.