C#: Await or return task in method that uses disposable objects

Feb 2, 2023
< >

I recently saw a video of Nick Chapsas about Settling the Biggest Await Async Debate in .NET. One topic was the usage of await vs returning a task in a method that deals with disposable objects. In this post I summarize the topic in my own words.

Introduce the scenario

Assume you have the following code.

// Scenario 1: Return the task.
Task scenarioDisposableObjectReturnTask()
{
    using var aObject = new DisposableClass();
    return AsyncStuff.DoAsyncStuff();
}

// Scenario 2: Await the task.
async Task scenarioDisposableObjectAwaitTask()
{
    using var aObject = new DisposableClass();
    await AsyncStuff.DoAsyncStuff();
}

What do you think happens with the disposable object? Does it still exist while the async code is executed?

We can take a look at the execution of the code (showed by console outputs).

Console.WriteLine("Start");
await scenarioDisposableObjectReturnTask();
Console.WriteLine("End");

/*
Output:
    Start
    I dispose some things. This should happen after all work is done.
    Task delay started.
    Task delay ended.
    End
*/

Console.WriteLine("Start");
await scenarioDisposableObjectAwaitTask();
Console.WriteLine("End");

/*
Output:
    Start
    Task delay started.
    Task delay ended.
    I dispose some things. This should happen after all work is done.
    End
*/


// ---------- DETAILS ----------

// Only necessary to show the dispose event.
class DisposableClass : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("I dispose some things. This should happen after all work is done.");
    }
}

// Do something async
internal static class AsyncStuff
{ 
    public static Task DoAsyncStuff()
    {
        return Task.Run(async () =>
        {
            Console.WriteLine("Task delay started.");
            await Task.Delay(5000);
            Console.WriteLine("Task delay ended.");
        });
    }
}

In the first scenario (return the task) the object could be disposed when the async code gets executed. I think this is not obvious to everyone during development.

Why it’s disposed in the first scenario?

Look at the translated code and you can see the reason.

DisposableClass disposableClass = new DisposableClass();
try
{
    // The task is returned immediately... 
    return AsyncStuff.DoAsyncStuff();
}
finally
{
    // ... therefore the object is also disposed before the task is finished.
    if (disposableClass != null)
    disposableClass.Dispose();
}
</ >
Impressum