Michael Hagar
Michael Hagar

Reputation: 646

How to dispose of an object that it created in a task?

I have a foreach loop that creates multiple tasks like this:

[edit: CreateDisposableAsync returns a Task[IDisposable]]

foreach(...)
{
   tasks.Add(CreateDisposableAsync());
}

and later I await on all of these tasks, and catch any exceptions:

try
{
   await Task.WhenAll(tasks);
}
catch (AggregateException)
{
   // handle exceptions
}

but the call to CreateDisposableAsync() returns an IDisposable, which I want to be disposed whether or not there was an exception in any of the tasks. How can I do this?

[Edit: It turns out that the CreateDisposableAsync() function was disposing of its created object if itself threw an exception, so there was nothing wrong with the original code.]

Upvotes: 3

Views: 2290

Answers (3)

Michael Hagar
Michael Hagar

Reputation: 646

I'm posting this here because it seems like an alternative solution:

    private static async Task CallCreateDisposableAsync()
    {
         using (await CreateDisposableAsync()) { }
    }

and then

foreach(...)
{
   tasks.Add(CallCreateDisposableAsync);
}

that way the using statement can dispose the created IDisposable.

Upvotes: 1

Igor
Igor

Reputation: 62228

From the comments

Q: with the exception of disposing the result do you do (or want to do) anything else with the result in the calling code

A: No I do not

The easiest way to do this is to have the CreateDisposableAsync method clean up its own resources before it returns and return Task instead of Task<IDisposable>. The existing calling code shown in the OP would not have to change.

// change the signature
async Task CreateDisposableAsync(){
   // use using blocks for anything that needs to be disposed
   // try/finally is also acceptable
   using(var someDisposableInstance = new SomethingDisposable()){
      // implementation
   }
}

Upvotes: 3

Scott Chamberlain
Scott Chamberlain

Reputation: 127573

Only tasks that ran to completion will have returned objects that could be disposed. Just filter the list to the tasks that completed then select the results.

try
{
    try
    {
       await Task.WhenAll(tasks);
    }
    catch (AggregateException)
    {
       // handle exceptions
    }

    //do other stuff with the returned task objects.
}
finally
{    
    foreach(var item in tasks.Where(x=>x.Status == TaskStatus.RanToCompletion).Select(x=>x.Result))
    {
        //We use a try block so if Dispose throws it does not break the loop.
        try
        {
            item.Dispose();
        }
        catch(Exception ex)
        {
            //Log any exception on dispose.
        }
    }
}

Or if you do not plan on doing any other work after the WaitAll

try
{
   await Task.WhenAll(tasks);
}
catch (AggregateException)
{
   // handle exceptions
}
finally
{    
    foreach(var item in tasks.Where(x=>x.Status == TaskStatus.RanToCompletion).Select(x=>x.Result))
    {
        //We use a try block so if Dispose throws it does not break the loop.
        try
        {
            item.Dispose();
        }
        catch(Exception ex)
        {
            //Log any exception on dispose.
        }
    }
}

Tasks that threw errors don't return objects, there is no way to dispose of them outside of CreateDisposableAsync() and it would be that function's responsibility to dispose of them if there was any kind of error.

public async Task<MyDisposeableClass> CreateDisposableAsync()
{
    MyDisposeableClass myDisposeableClass = null;
    try
    {
        myDisposeableClass = new MyDisposeableClass();

        //...

        return myDisposeableClass;
    }
    catch
    {
        //dispose of the class if the instance was created.
        if(myDisposeableClass != null)
            myDisposeableClass.Dispose();

        //let the execption bubble up.
        throw;
    }
}

Upvotes: 2

Related Questions