最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

.net - Why isn't the object collected by GC in C# when assigned null and GC.Collect() is called, despite a background ta

programmeradmin1浏览0评论

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


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


Share Improve this question edited Jan 24 at 13:49 Cavid Haciyev asked Jan 24 at 13:30 Cavid HaciyevCavid Haciyev 2991 silver badge10 bronze badges 5
  • 9 The background thread itself has a reference to Starter, as it needs to increment Starter.a. – canton7 Commented Jan 24 at 13:34
  • 1 Background task is holding a reference to it, and that task is being held by the scheduler. I don't understand what the question is, why would you expect it to be collected? – Charlieface Commented Jan 24 at 13:34
  • 5 Even without Task: a thread won't just die because the GC did a collection. Threads keep objects alive, not the other way around – canton7 Commented Jan 24 at 13:36
  • 1 Note, it is likely that neither the second thread nor the background task have even started by the time GC.Collect() is called and completed; but the fact that the delegate (with the Starter as the target) is in the queue is more than sufficient to keep the object alive. – Marc Gravell Commented Jan 24 at 13:36
  • 2 Please be aware that you've chosen to work in a managed language with automatic memory management. In general, in such a situation, you shouldn't be trying then to micro-manage yourself how the memory management happens. – Damien_The_Unbeliever Commented Jan 24 at 13:36
Add a comment  | 

2 Answers 2

Reset to default 4

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.

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

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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论