-
-
Notifications
You must be signed in to change notification settings - Fork 370
Open
Description
Description
Hi! I am reporting an issue that I discovered after reading a comment by Servy here. In case the factory delegate of an AsyncLazy<T> instance closes over some expensive resource, this resource will not be eligible for garbage collection after the completion of the delegate.
Reproduction Steps
public static async Task Main()
{
Console.WriteLine($"TotalMemory: {GC.GetTotalMemory(true):#,0} bytes");
var lazy = new Nito.AsyncEx.AsyncLazy<int>(GetFunction());
Console.WriteLine($"AsyncLazy result: {await lazy:#,0}");
await Task.Yield();
GC.Collect();
Console.WriteLine($"TotalMemory: {GC.GetTotalMemory(true):#,0} bytes");
GC.KeepAlive(lazy);
static Func<Task<int>> GetFunction()
{
byte[] bytes = new byte[20_000_000];
return new Func<Task<int>>(async () =>
{
await Task.Delay(200);
return bytes.Length;
});
}
}Output:
TotalMemory: 86,192 bytes
AsyncLazy result: 20,000,000
TotalMemory: 20,133,704 bytes
Expected behavior
The GC.GetTotalMemory() after awaiting the AsyncLazy<T> should be roughly the same as before.
Actual behavior
The GC.GetTotalMemory() after awaiting the AsyncLazy<T> is around 20 MB more than before.
Configuration
- .NET 6.0
- Nito.AsyncEx 5.1.2
- Console application
- Release built
Other information
After switching to a simpler AsyncLazy<T> implementation like the one below, the problem is not reproduced:
private class AsyncLazySimple<T>
{
private readonly Lazy<Task<T>> _lazyTask;
public AsyncLazySimple(Func<Task<T>> factory) => _lazyTask = new(() => factory());
public Task<T> Task => _lazyTask.Value;
public TaskAwaiter<T> GetAwaiter() => Task.GetAwaiter();
}Output:
TotalMemory: 86,192 bytes
AsyncLazy result: 20,000,000
TotalMemory: 133,728 bytes
Metadata
Metadata
Assignees
Labels
No labels