C# Weak-referenced object alive after `using` block
Based on this previous question, I wanted to see if not disposing an HttpClient
object creates leaks (which does, but nothing new here). However, what I’ve also found is that if I don’t dispose a HttpClient
object, the returned HttpResponseMessage
is not garbage collected.
Here’s the test. I’ve provided a reproducing fiddle here.
using System; using System.Net.Http; namespace HttpClientTest { internal static class Program { private static T CallInItsOwnScope<T>(Func<T> getter) { return getter(); } private static void Main() { var client = new HttpClient(); var wRef2 = CallInItsOwnScope(() => { using (var response = client.GetAsync(new Uri("https://postman-echo.com/get?foo1=bar1&foo2=bar2")).Result) { return new WeakReference(response); } }); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine($"Alive: {wRef2.IsAlive}"); client.Dispose(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine($"Alive: {wRef2.IsAlive}"); Console.ReadKey(); } } }
Compiling under .NET Framework 4.7.2, The output is as follows:
Alive: True Alive: False
Edit: using .NET Core, I get the expected result:
Alive: False Alive: False
My question is, why HttpResponseMessage
is still alive after I disposed it, but only if I don’t dispose it’s creator?
(Looking at HttpClient
‘s sources I found that there is indeed a reference to the HttpResponseMessage
being held, but this is deep into async land for me to really understand what’s going on (SetTaskCompleted
-> TaskCompletionSource<>.TrySetResult()
))
First of all, we need look at this MSDN – HttpClient Class.
HttpClient is intended to be instantiated once per application, rather than per-use. See Remarks.
Anyway, here is what you missed.
public void CallServiceTest() { var wRef2 = CallInItsOwnScope(() => { // HttpClient -> HttpMessageInvoker(IDisposable), so it can be disposed. using (var client = new HttpClient()) { using (var response = client.GetAsync(new Uri("https://postman-echo.com/get?foo1=bar1&foo2=bar2")).Result) { return new WeakReference(response); } } }); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsFalse(wRef2.IsAlive); }