C# 线程在完成后如何导致内存泄漏?
How do C# threads cause memory leaks when they already finished?
这是我的代码:
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()
函数 returns 之前所有的 TestObjects 都得到垃圾收集?
编辑:我刚刚在 Visual Studio (.NET) 中测试了完全相同的代码,无论该行是否被注释掉,所有对象都被垃圾回收。
因此,我现在认为这个问题是一个特定于 Mono 的问题,并且一开始没有内存泄漏。
终结器不是确定性的。你不能指望他们被召唤。
如果清理相关资源对您的程序至关重要,那么您应该明确处理它,而不是依赖终结器。
如果清理资源会很好,但您并不真正关心终结器是否访问它,那么您可以选择不显式处置非托管资源。
另请注意,使托管 object 符合垃圾 条件 collection 并不一定意味着它将被垃圾收集。这意味着它 可以 收藏家随时收集。
最后,认识到中止线程是另一件不可靠的事情。线程做。有多种方法可以让已请求中止的线程无法成功中止,或者当它中止时会导致许多不同类型的问题。你应该避免使用 Thread.Abort
除非有问题的线程被设计为被中止,并且你对试图推理一个可能在任何两个操作之间抛出异常的程序的所有许多可能的陷阱有深刻的理解。
这是我的代码:
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()
函数 returns 之前所有的 TestObjects 都得到垃圾收集?
编辑:我刚刚在 Visual Studio (.NET) 中测试了完全相同的代码,无论该行是否被注释掉,所有对象都被垃圾回收。
因此,我现在认为这个问题是一个特定于 Mono 的问题,并且一开始没有内存泄漏。
终结器不是确定性的。你不能指望他们被召唤。
如果清理相关资源对您的程序至关重要,那么您应该明确处理它,而不是依赖终结器。
如果清理资源会很好,但您并不真正关心终结器是否访问它,那么您可以选择不显式处置非托管资源。
另请注意,使托管 object 符合垃圾 条件 collection 并不一定意味着它将被垃圾收集。这意味着它 可以 收藏家随时收集。
最后,认识到中止线程是另一件不可靠的事情。线程做。有多种方法可以让已请求中止的线程无法成功中止,或者当它中止时会导致许多不同类型的问题。你应该避免使用 Thread.Abort
除非有问题的线程被设计为被中止,并且你对试图推理一个可能在任何两个操作之间抛出异常的程序的所有许多可能的陷阱有深刻的理解。