Post

IDisposable, IAsyncDisposable, and Tasks: Dispose Behavior With Tasks In C#

While working with HTTP calls that return a Stream via HttpClient’s var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead); -> var stream = await response.Content.ReadAsStreamAsync(); method for efficient handling of streamed data Brian Dunnington identified the response should be disposed and pointed me to https://www.stevejgordon.co.uk/using-httpcompletionoption-responseheadersread-to-improve-httpclient-performance-dotnet.

Working through it I found disposing a Task<T> itself doesn’t appear to call underlying Dispose/DisposeAsync methods on the generic T. Instead, the T itself, in this case a Stream, should be disposed.

Also, https://devblogs.microsoft.com/pfxteam/do-i-need-to-dispose-of-tasks/ indicates that disposing of tasks is probably not necessary, but rather something to consider to meet performance goals. For what it’s worth this post is dated in 2012, prior to the release of .NET Core.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Threading.Tasks;
                    
public class Program
{
    public static async Task Main()
    {
        //
        // IDisposable
        //
        
        var myDisposableTask = Task.FromResult(new MyDisposable());
        
        myDisposableTask.Dispose();
        
        MyDisposable myDisposable;
        
        using (myDisposable = await myDisposableTask)
        {
            Console.WriteLine("myDisposable.IsDisposed inside using: "
                + myDisposable.IsDisposed /* == False */);
        };
        
        Console.WriteLine("myDisposable.IsDisposed outside using: "
            + myDisposable.IsDisposed /* == True */);
        
        //
        // IAsyncDisposable
        //
        
        var myAsyncDisposableTask = Task.FromResult(new MyAsyncDisposable());
        
        myAsyncDisposableTask.Dispose();
        
        MyAsyncDisposable myAsyncDisposable;
        
        await using (myAsyncDisposable = await myAsyncDisposableTask)
        {
            Console.WriteLine("myAsyncDisposable.IsDisposed inside await using: "
                + myAsyncDisposable.IsDisposed /* == False */);
        }
        
        Console.WriteLine("myAsyncDisposable.IsDisposed outside await using: "
            + myAsyncDisposable.IsDisposed /* == True */);
    }
}

public class MyDisposable : IDisposable
{
    public bool IsDisposed { get; private set; }
    
    public void Dispose()
    {
        IsDisposed = true;
    }
}

public class MyAsyncDisposable : IAsyncDisposable
{
    public bool IsDisposed { get; private set; }

    public ValueTask DisposeAsync()
    {
        IsDisposed = true;
        
        return ValueTask.CompletedTask;
    }
}
This post is licensed under CC BY 4.0 by the author.