In the code below, the Starter object is set to null, and GC.Collect() is called, but the background task still runs and the object isn't collected:
namespace ConsoleApp5
{
internal class Program
{
static void Main(string[] args)
{
Starter s = new();
s.Start();
s = null;
GC.Collect(0, GCCollectionMode.Forced);
GC.Collect(1, GCCollectionMode.Forced);
GC.Collect(2, GCCollectionMode.Forced);
Console.ReadLine();
}
}
public class Starter
{
int a = 0;
public void Start()
{
Task.Run(() =>
{
while (true)
{
a++;
Console.WriteLine(a);
}
});
}
}
}
Thread invocation methods (and thus their targets) count as a GC root, so the moment Start()
is invoked, the object is rooted by the thread machinery. That thread is short-lived and only serves to create a thread-pool item - and almost certainly hasn't even started by the time GC.Collect()
executes (and completes), but even if it had finished: the thread-pool queue and active workers are also rooted (via the thread-pool machinery). Since a
is a field on the object, and the thread-pool callback touches that field: the thread-pool task can see the object, thus it is not collectable.
However, it is very likely that at the time GC.Collect()
executes, the ThreadStart
delegate is still in a pending queue waiting for the thread to be fully created and activated - but since the ThreadStart
has a target of the Starter
object you created: it is still reachable and thus not collectable.