【问题标题】:How do C# threads cause memory leaks when they already finished?C# 线程在完成后如何导致内存泄漏?
【发布时间】:2015-04-30 10:51:11
【问题描述】:

这是我的代码:

using System;
using System.Collections.Generic;
using System.Threading;
namespace testt
{
    class MainClass
    {
        public static List<TestObject> testjobs = new List<TestObject> ();
        public static void Main (string[] args)
        {
            Console.WriteLine ("Hello World!");
            addTask (); //put a breakpoint here
            Thread.Sleep (5000);
            deleteObj ();
            while (true) {
                Console.WriteLine ("MAIN STILL EXISTS!");
                Thread.Sleep (1500);
                GC.Collect ();
            }
        }
        public static void addTask()
        {
            for (int i = 0; i < 10; i++)
            {
                testjobs.Add (new TestObject ());
                testjobs [i].Start ();
            }
        }

        public static void deleteObj()
        {
            for(int i=0; i<10;i++)
            {
                testjobs [0].dispose ();
                testjobs.RemoveAt (0);
            }
            Console.WriteLine (testjobs.Count);
        }

    }

    public class TestObject
    {

        private bool _isStopRequested;
        private Thread _thread;
        public void Start()
        {
            _thread = new Thread(ThreadRoutine);
            _thread.Start();
        }

        public void Stop()
        {
            _isStopRequested = true;
            if(!_thread.Join(5000))
            {
                _thread.Abort();
            }
        }

        public void dispose(){
            this.Stop ();
            this._thread.Abort ();
            this._thread=null;
        }


        private void ThreadRoutine()
        {
            //while(!_isStopRequested) //THIS CAUSES THE MEMORY LEAK!!!
            {
            Thread.Sleep (1);
            }
            Console.WriteLine ("THREAD FINISHED");
        }

        ~TestObject(){
            Console.WriteLine ("===================TESTOBJECT DESTROYED!!===============");
        }
    }
}

如果你在 //while(!_isStopRequested) 未注释的情况下运行它,TestObject 实例将不会被销毁,即不会调用它们的析构方法。

如果按原样运行,则只会销毁 4-8 个对象,而不是全部 10 个。

为什么当线程完全退出时会发生这种情况?我检查了 Xamarin 调试器,线程肯定已停止。如果您在 Xamarin 中的 addTask() 处放置断点;那么你可以看到那10个线程

对此我唯一的解释是线程以某种方式保留了对其父对象 TestObject 实例的引用,即使在它们完成后也是如此。当线程已经完成时,线程如何持有对其父对象的引用?

另外,如果我将Thread.Sleep(1) 更改为Thread.Sleep(5000)TestObjects 也将停止被收集。

另外,事实上,只有一些 TestObjects 被收集,而另一些则没有。

为什么会发生这些事情?如何确保在 deleteObj() 函数返回时所有的 TestObjects 都得到垃圾回收?

编辑:我刚刚在 Visual Studio (.NET) 中测试了完全相同的代码,并且所有对象都被垃圾收集,无论该行是否被注释掉。

因此,我现在认为这个问题是 Mono 特有的问题,并且从一开始就没有内存泄漏。

【问题讨论】:

    标签: c# multithreading memory-leaks mono xamarin


    【解决方案1】:

    终结器不是确定性的。你不能依赖他们被调用。

    如果清理相关资源对您的程序至关重要,那么您应该明确处置它,而不是依赖终结器。

    如果清理资源会很好,但您并不真正关心终结器是否会处理它,那么您可以选择不显式处理非托管资源。

    另请注意,使托管对象符合垃圾回收条件并不一定意味着它将被垃圾回收。这意味着它可以在收藏者喜欢它的时候收集。

    最后,认识到中止线程是另一件不可靠的事情。线程做。被请求中止的线程有多种方法不会成功地这样做,或者当它这样做时会导致许多不同类型的问题。您应该避免使用Thread.Abort,除非所讨论的线程被设计为中止,并且您对尝试推理可能在任何两个操作之间引发异常的程序的所有许多可能的陷阱有深刻的理解。

    【讨论】:

    • 那么我该如何明确地处置对象呢?
    • @joesmith 实现IDisposable 并在您想要处理任何未管理的对象时始终在对象上调用Dispose。如果您没有任何非托管对象,那么您根本不必担心它,并允许 GC 随时清理对象。
    • 问题是我的内存使用量在我的对象被“删除”之后继续增加。除了我可以看到的一些布尔变量之外,该对象不包含“资源”。线程资源也是如此吗?我认为线程也被终止了。如何让它释放所有内存?
    • @joesmith 您的程序充满了不可靠的操作,您根本无法依赖这些操作来做任何事情。有这么多操作可以做任何数量的未定义的事情,意外发生的几率非常高。相反,正确的程序是确定性的,并且可以可靠地推断以预期的方式执行,如果您计划对您的程序做什么有任何期望。
    • 哪些操作不可靠,哪些操作可靠,如何将不可靠的操作替换为可靠操作?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-21
    • 2011-09-23
    • 2021-02-25
    • 1970-01-01
    • 2022-11-06
    • 2020-08-08
    • 2021-07-23
    相关资源
    最近更新 更多