Alex
Alex

Reputation: 77329

List of objects with async Task methods, execute all concurrently

Given the following:

BlockingCollection<MyObject> collection;

public class MyObject
{
   public async Task<ReturnObject> DoWork() 
   {       
      (...) 
      return await SomeIOWorkAsync();
   }
}

What would be the correct/most performant way to execute all DoWork() tasks asynchronously on all MyObjects in collection concurrently (while capturing the return object), ideally with a sensible thread limit though (I believe the Task Factory/ThreadPool does some management here)?

Upvotes: 5

Views: 6158

Answers (4)

Stephen Cleary
Stephen Cleary

Reputation: 456457

What would be the correct/most performant way to execute all DoWork() tasks asynchronously on all MyObjects in collection concurrently (while capturing the return object), ideally with a sensible thread limit

The easiest way to do that is with Task.WhenAll:

ReturnObject[] results = await Task.WhenAll(collection.Select(x => x.DoWork()));

This will invoke DoWork on all MyObjects in the collection and then wait for them all to complete. The thread pool handles all throttling sensibly.

Is there a different way if I want to capture every individual DoWork() return immediately instead of waiting for all items to complete?

Yes, you can use the method described by Jon Skeet and Stephen Toub. I have a similar solution in my AsyncEx library (available via NuGet), which you can use like this:

// "tasks" is of type "Task<ReturnObject>[]"
var tasks = collection.Select(x => x.DoWork()).OrderByCompletion();
foreach (var task in tasks)
{
  var result = await task;
  ...
}

Upvotes: 1

svick
svick

Reputation: 244767

ThreadPool manages the number of threads running, but that won't help you much with asynchronous Tasks.

Because of that, you need something else. One way to do this is to utilize ActionBlock from TPL Dataflow:

int limit = …;
IEnumerable<MyObject> collection = …;

var block = new ActionBlock<MyObject>(
    o => o.DoWork(),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = limit });

foreach (var obj in collection)
    block.Post(o);

block.Complete();
await block.Completion;

Upvotes: 3

Nick Butler
Nick Butler

Reputation: 24383

My comment was a bit cryptic, so I though I'd add this answer:

List<Task<ReturnObject>> workTasks =
  collection.Select( o => o.DoWork() ).ToList();

List<Task> resultTasks =
  workTasks.Select( o => o.ContinueWith( t =>
      {
        ReturnObject r = t.Result;
        // do something with the result
      },
      // if you want to run this on the UI thread
      TaskScheduler.FromCurrentSynchronizationContext()
    )
  )
  .ToList();

await Task.WhenAll( resultTasks );

Upvotes: 0

You can make use of the WhenAll extension method.

var combinedTask = await Task.WhenAll(collection.Select(x => x.DoWork());

It will start all tasks concurrently and waits for all to finish.

Upvotes: 8

Related Questions