Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by SolarSatellite463

Why isn’t the Starter object collected by GC when its background task still holds a reference?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

In the code below, a Starter object is created and used to run a background task. After starting the task, the object is set to null and GC.Collect() is called for all generations, but the object still isn’t collected.

CSHARP
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); } }); } } }

The background task’s lambda captures the instance member (the field a), so even after setting the Starter object to null the closure still holds a reference. This prevents the object from being garbage collected.

4

Comments 0

Answers 3

Views 174

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

The background task is capturing and using the instance member "a", which means the lambda closure keeps a reference to the Starter object, preventing it from being garbage collected. Even when you set s to null, the task’s closure still refers to that object.

In other words, while the task runs indefinitely (due to the infinite loop), the Starter instance remains rooted and isn’t eligible for collection. This is why calling GC.Collect() has no effect on the object in this scenario.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by MeteoricCosmonaut011 1 month ago

0

Just to add to Marc Gravell's excellent answer a bit of simplified illustration based on the initial example:

CSHARP
internal class Program { static void Main(string[] args) { Starter s = new(); Console.WriteLine(s.GetHashCode()); s.Start(); s = null; GC.Collect(0, GCCollectionMode.Forced); GC.Collect(1, GCCollectionMode.Forced); GC.Collect(2, GCCollectionMode.Forced); Console.WriteLine("-----------"); ThreadInfrastructure.Repository .FirstOrDefault().Invoke(); Console.ReadLine(); } } public static class ThreadInfrastructure { public static List<Action> Repository = new(); } public class Starter { private int _a = 0; public int a { get => _a; set { Console.WriteLine(this.GetHashCode()); _a = value; } } public void Start() { Action action = () => { a++; Console.WriteLine(a); }; ThreadInfrastructure.Repository.Add(action); } }

Possible output:

23458411
-----------
23458411
1

The ThreadInfrastructure has a list which has a reference to the Action which has a reference to our object. So it's not eligible for garbage collection because it is reachable.

No comments yet.

Answer by SupernovaStargazer983 1 month ago

0

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.

No comments yet.

Discussion

No comments yet.