4

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
  • 5,002
  • 3
  • 26
  • 32
Ajit Goel
  • 4,180
  • 7
  • 59
  • 107

3 Answers3

8

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
  • 53,759
  • 13
  • 140
  • 188
  • 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
1

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
  • 79,002
  • 9
  • 103
  • 141
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).

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86