3

I am trying to optimize this code to decrease the time taken to complete the forloop. In this case, CreateNotification() takes a long time and using async await does not improve performance as each asynchronous call is being awaited. I would like to use Task.WhenAll() to optimize the code. How can I do this?

foreach (var notification in notificationsInput.Notifications)
{
  try
  {
    var result = await CreateNotification(notification);
    notification.Result = result;          
  }
  catch (Exception exception)
  {
    notification.Result = null;
  }
  notifications.Add(notification);
}
Nikhil Vartak
  • 4,822
  • 3
  • 23
  • 31
Ajit Goel
  • 3,732
  • 3
  • 48
  • 91

3 Answers3

7

You can call Select on the collection whose elements you want to process in parallel, passing an asynchronous delegate to it. This asynchronous delegate would return a Task for each element that's processed, so you could then call Task.WhenAll on all these tasks. The pattern is like so:

var tasks = collection.Select(async (x) => await ProcessAsync(x));
await Task.WhenAll(tasks);

For your example:

var tasks = notificationsInput.Notifications.Select(async (notification) =>
{
    try
    {
        var result = await CreateNotification(notification);
        notification.Result = result;          
    }
    catch (Exception exception)
    {
        notification.Result = null;
    }
});
await Task.WhenAll(tasks);

This assumes that CreateNotification is thread-safe.

Douglas
  • 52,041
  • 10
  • 126
  • 179
  • Does this execute in parallel? I think this still executes asynchronously. – David B Jun 30 '19 at 15:15
  • @DavidB: The asynchronous tasks will execute in parallel. At the `await` keyword, control will be returned to the caller; thus, the `Select` would continue iterating over the rest of the notifications whilst the previous `CreateNotification` tasks are still in flight. – Douglas Dec 15 '19 at 17:35
2

Update

You will need to install DataFlow to use this solution

https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/


Depending on what CreateNotification is and whether you want to run this in parallel.

You could use a DataFlow ActionBlock, it will give you the best of both worlds if this is IO bound or Mix IO/CPU bound operations and let you run async and in parallel

public static async Task DoWorkLoads(NotificationsInput notificationsInput)
{
   var options = new ExecutionDataflowBlockOptions
                     {
                        MaxDegreeOfParallelism = 50
                     };

   var block = new ActionBlock<Something>(MyMethodAsync, options);

   foreach (var notification in notificationsInput.Notifications)
      block.Post(notification);

   block.Complete();
   await block.Completion;

}

...

public async Task MyMethodAsync(Notification notification)
{       
     var result = await CreateNotification(notification);
     notification.Result = result;    
}

Add pepper and salt to taste.

TheGeneral
  • 75,627
  • 8
  • 79
  • 119
1

I think this ought to be equivalent to your code:

var notifications = new ConcurrentBag<Notification>();
var tasks = new List<Task>();
foreach (var notification in notificationsInput.Notifications)
{
    var task = CreateNotification(notification)
                    .ContinueWith(t =>
                    {
                        if (t.Exception != null)
                        {
                            notification.Result = null;
                        }
                        else
                        {
                            notification.Result = t.Result;
                        }
                        notifications.Add(notification);
                    });
    tasks.Add(task);
}
await Task.WhenAll(tasks);

.ContinueWith( will receive the completed/failed task from CreateNotification(, and is itself a task. We add the ContinueWith task to a list and use that in the WhenAll(.

I'm using a ConcurrentBag for notifications so that you can add from multiple threads safely. If you want to turn this into a regular list, you can call var regularListNotifications = notifications.ToList(); (assuming you have a using for LINQ).

DiplomacyNotWar
  • 31,605
  • 6
  • 57
  • 75