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 CosmicScout054

Why isn’t my C# object garbage collected when a background task captures its instance?

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

I have a scenario where I create an instance of the Starter class, call its Start method to run a background task, then set the reference to null and force garbage collection. However, the object is not collected because the background task continuously accesses its instance member. This seems to be due to the lambda capturing the Starter instance, which creates a closure that keeps it rooted.

Here’s the example code:

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

I want to understand why the object isn’t being garbage collected even after calling GC.Collect() and how to modify the code if collection is desired.

4

Comments 0

Answers 3

Views 175

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

When calling Task.Run with a lambda that uses instance members, the lambda creates a closure that captures the instance of Starter. In your code, the background task continuously accesses the field a (through a++) and Console.WriteLine, which means that the Starter instance remains referenced by the delegate running on the thread pool. As a result, even though you set s to null, the object is still rooted because of the closure, so it isn’t eligible for garbage collection.

If you want the Starter object to be collected, you would need to ensure that no live references remain to it—for example, by avoiding capturing the instance in the lambda or by stopping the task’s execution.

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 InterstellarGuardian087 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

No comments yet.

Answer by PlanetaryCollector498 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.