Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Await Async WhenAll with Disposable Objects

I've hit a problem trying to process multiple Tasks in parallel (or however the runtime feels fit) when using disposable objects. In the following code snippet, each Processor object is immediately disposed BEFORE it has done the required work.

async public Task ProcessData(IEnumerable<int> data)
{
    var tasks = new List<Task>();

    foreach (var d in data)
    {
        using (var processor = new Processor(d))
        {
            processor.Completed += (sender, e) => { // Do something else };
            tasks.Add(processor.ProcessAsync());
        }
    }

    await Task.WhenAll(tasks);
}

Re-writing the code as below results in the each Processor performing its processing and THEN being disposed, but it's not the most efficient way to run multiple Tasks that don't depend on one another.

async public Task ProcessData(IEnumerable<int> data)
{
    foreach (var d in data)
    {
        using (var processor = new Processor(d))
        {
            processor.Completed += (sender, e) => { // Do something else };
            await processor.ProcessAsync();
        }
    }
}

Can someone please explain why the first example is disposing 'early' and give an example of the best code pattern for this situation.

like image 288
Chris Arnold Avatar asked Jul 22 '13 20:07

Chris Arnold


1 Answers

It helps to think of await as pausing the current method, even though it doesn't block the thread.

In your first example, when you are executing the foreach loop, each time you create a Processor, start an operation (saving the operation's Task in a list), and then dispose the Processor. After your foreach loop is finished, then you (asynchronously) wait for all the operations to complete.

In your second example, when you are executing the foreach loop, each time you create a Processor, start an operation, (asynchronously) wait for it to complete, and then dispose the Processor.

To fix this, you should write a helper method, as such:

private static async Task ProcessData(int data)
{
  using (var processor = new Processor(d))
  {
    processor.Completed += (sender, e) => { /* Do something else */ };
    await processor.ProcessAsync();
  }
}

Your helper method defines a higher-level operation, which will take care of disposing its own Processor resource at the appropriate time. Then you can start all your work concurrently as such:

public async Task ProcessData(IEnumerable<int> data)
{
  ...
  await Task.WhenAll(data.Select(d => ProcessData(d)));
  ...
}
like image 175
Stephen Cleary Avatar answered Nov 06 '22 06:11

Stephen Cleary