I ran into a problem when it seems that local resources cannot be cleaned up during garbage collection if the resources are in an asynchronous wait method.
I created a code sample to illustrate the problem.
Simpleclass
SimpleClass uses a static counter to record the number of active instances by increasing the _count static field during construction and decreasing the same field during destruction.
using System;
namespace AsyncGarbageCollector
{
public class SimpleClass
{
private static readonly object CountLock = new object();
private static int _count;
public SimpleClass()
{
Console.WriteLine("Constructor is called");
lock (CountLock)
{
_count++;
}
}
~SimpleClass()
{
Console.WriteLine("Destructor is called");
lock (CountLock)
{
_count--;
}
}
public static int Count
{
get
{
lock (CountLock)
{
return _count;
}
}
}
}
}
Program
Here is the main program that has three tests
- The standard call that initializes the class and then the variable is left unchanged.
- An asynchronous call that initializes the class and then the variable is left out of scope
- , , null ,
GC.Collect. , .
using System;
using System.Threading.Tasks;
namespace AsyncGarbageCollector
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press 1, 2 or 3 to start.\n\n");
var code = Console.ReadKey(true);
if (code.Key == ConsoleKey.D1)
RunTest1();
else if (code.Key == ConsoleKey.D2)
RunTest2Async().Wait();
else if (code.Key == ConsoleKey.D3)
RunTest3Async().Wait();
Console.WriteLine("\n\nPress any key to close.");
Console.ReadKey();
}
private static void RunTest1()
{
Console.WriteLine("Test 1\n======");
TestCreation();
DisplayCounts();
}
private static async Task RunTest2Async()
{
Console.WriteLine("Test 2\n======");
await TestCreationAsync();
DisplayCounts();
}
private static async Task RunTest3Async()
{
Console.WriteLine("Test 3\n======");
await TestCreationNullAsync();
DisplayCounts();
}
private static void TestCreation()
{
var simple = new SimpleClass();
}
private static async Task TestCreationAsync()
{
var simple = new SimpleClass();
await Task.Delay(50);
}
private static async Task TestCreationNullAsync()
{
var simple = new SimpleClass();
await Task.Delay(50);
Console.WriteLine("Setting Null");
simple = null;
}
private static void DisplayCounts()
{
Console.WriteLine("Running GC.Collect()");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Count: " + SimpleClass.Count);
}
}
}
Test 1
======
Constructor is called
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0
Test 2
======
Constructor is called
Running GC.Collect()
Count: 1
Returned to Main
Running GC.Collect()
Destructor is called
Count: 0
Test 3
======
Constructor is called
Setting Null
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0
2 SimpleClass ( ), .
? , "", , , , .
. ?
/ .