.net 线程的垃圾收集

.net 线程的垃圾收集,.net,multithreading,.net,Multithreading,我是否需要保护线程对象不受垃圾收集器的影响?那么包含线程运行的函数的对象呢 考虑这个简单的服务器: class Server{ readonly TcpClient client; public Server(TcpClient client){this.client = client;} public void Serve(){ var stream = client.GetStream(); var writer = new Str

我是否需要保护
线程
对象不受垃圾收集器的影响?那么包含线程运行的函数的对象呢

考虑这个简单的服务器:

class Server{
    readonly TcpClient client;

    public Server(TcpClient client){this.client = client;}

    public void Serve(){
        var stream = client.GetStream();
        var writer = new StreamWriter(stream);
        var reader = new StreamReader(stream);

        writer.AutoFlush = true;
        writer.WriteLine("Hello");

        while(true){
            var input = reader.ReadLine();
            if(input.Trim() == "Goodbye")
                break;
            writer.WriteLine(input.ToUpper());
        }

        client.Close();
    }
}
static void Main(string[] args){
    var listener = new TcpListener(IPAddress.Any, int.Parse(args[0]));
    listener.Start();

    while(true){
        var client = listener.AcceptTcpClient();
        var server = new Server(client);
        var thread = new Thread(server.Serve);
        thread.Start();
    }
}
我是否应该将线程对象包装在某种静态收集中,以防止它们被垃圾收集器清除

假设,如果
线程
对象本身保持活动状态,那么
服务器
对象将保持活动状态,因为线程持有对委托的引用,委托持有对目标对象的引用。或者可能收集了线程对象本身,但实际线程继续运行。现在,
服务器
对象可以收集了。如果它试图访问其字段,会发生什么情况

垃圾收集有时让我头晕目眩。我很高兴我通常不用去想它


考虑到这里可能存在的问题,我认为垃圾收集器足够聪明,在线程本身仍在执行时不会收集线程对象,但我找不到任何文档这样说。Reflector在这里没有什么帮助,因为很多
线程
类都是在
MethodImplOptions.InternalCall
函数中实现的。我不想翻阅我那本过时的SSCLI来寻找答案(既因为这很痛苦,也因为它不是一个确定的答案)。

这很简单。真正的执行线程不是线程对象。该程序在真实的Windows线程中执行,无论.NET垃圾收集器对线程对象做了什么,这些线程都保持活动状态。所以对你来说是安全的;如果只想让程序继续运行,就不需要关心线程对象

还要注意,线程在运行时不会被收集,因为它们实际上属于应用程序“根”。(根-垃圾收集器如何知道什么是活动的。)


更多详细信息:托管线程对象可通过
Thread.CurrentThread
访问-这类似于全局静态变量,不会收集这些变量。正如我之前所写的:任何已启动并正在执行任何代码(即使在.NET之外)的托管线程都不会丢失其线程对象,因为它与“根”紧密相连。

只要线程正在执行,它就不会被收集。

start参数(在您的情况下是server.service)在中,有一位您已经认识的代表

假设线程对象 自身保持活动状态,然后服务器保持活动状态 对象将处于活动状态,因为线程 保存对委托的引用 其中包含对目标的引用 反对

这就是乔恩·斯基特(Jon Skeet)在《深度》中对代表目标生命的描述

值得注意的是 实例将阻止其目标 正在进行垃圾收集,如果 委托实例本身不能为空 收集

因此,只要
var线程
在范围内,就不会收集yes
server
。如果
var thread
在调用thread.start之前超出范围(并且没有其他引用),则可以收集它

现在是一个大问题。调用Thread.Start(),并且在
server.service
完成之前线程已超出范围。GC是否可以收集线程

与其四处挖掘,不如测试一下

class Program
{
    static void Main(string[] args)
    {
        test();
        GC.Collect(2);
        GC.WaitForPendingFinalizers();
        Console.WriteLine("test is over");
    }

    static void test()
    {
        var thread = new Thread(() =>  {
            long i = 0;

            while (true)
            {
                i++;   
                Console.WriteLine("test {0} {1} {2} ", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId i);
                Thread.Sleep(1000); //this is a demo so its okay
            }
        });
        thread.Name = "MyThread";
        thread.Start();

    }

}
这是输出。(添加threadID后)

所以我调用一个方法来创建一个线程并启动它。方法结束时,
var线程
超出范围。但是,即使我已经引入了一个GC,并且线程变量超出了范围,线程仍会继续运行

因此,只要在线程超出范围之前启动线程,一切都将按预期工作

更新 澄清

强制立即对所有代进行垃圾收集

任何可以收集到的东西都将被销毁。这并不像评论所说的那样“仅仅是一种意图的表达”。(如果是这样的话,就没有理由警告不要叫它)但是我添加了参数“2”,以确保它是gen0到gen2

然而,你关于终结器的观点值得注意。所以我加了一个

。。。挂起当前线程,直到处理终结器队列的线程清空该队列

这意味着需要处理的任何终结器实际上都已处理

示例的要点是,只要启动线程,它就会一直运行,直到它被中止或完成,并且GC不会因为
var线程
超出范围而以某种方式中止线程

作为旁白 Al Kepp的观点是正确的,即一旦启动线程,System.Threading.thread就会被根化。您可以通过使用SOS扩展来了解这一点

e、 g

是的,是的

这是为了什么

!GCRoot 0113bf40
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 7708 OSTHread 1e1c
Scan Thread 4572 OSTHread 11dc
Scan Thread 9876 OSTHread 2694
ESP:101f7ec:Root:  0113bf40(System.Threading.Thread)
ESP:101f7f4:Root:  0113bf40(System.Threading.Thread)
DOMAIN(0017FD80):HANDLE(Strong):9b11cc:Root:  0113bf40(System.Threading.Thread)
正如《圣经》所说,它是根

注意!GCRoot在启动后运行。在启动之前,它没有手柄(坚固)

如果输出包含句柄(强), 找到了有力的参考。这 表示对象是根对象,并且 无法进行垃圾收集。其他 可以在中找到引用类型 附录

有关托管线程如何映射到操作系统线程(以及如何使用SOS扩展进行确认)的更多信息,请参阅Yun Jin

尽管我担心在涉及多线程或GC(更不用说两者)时,用测试代码证明问题很困难,但我自己还是做了一个简单的小测试。我认为调查结果令人满意地解决了这个问题

我越是阅读这里贴出的答案,越是思考,它似乎越能归结为一个具体的问题:

线程
对象的终结器在我的线程退出之前运行时会造成多大的破坏

显然,GC本身会
!do 0113bf40
Name:        System.Threading.Thread
MethodTable: 79b9ffcc
EEClass:     798d8ed8
Size:        48(0x30) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79b88a28  4000720        4 ....Contexts.Context  0 instance aaef947d00000000 m_Context
79b9b468  4000721        8 ....ExecutionContext  0 instance aaef947d00000000 m_ExecutionContext
79b9f9ac  4000722        c        System.String  0 instance 0113bf00 m_Name
79b9fe80  4000723       10      System.Delegate  0 instance 0113bf84 m_Delegate
79ba63a4  4000724       14 ...ation.CultureInfo  0 instance aaef947d00000000 m_CurrentCulture
79ba63a4  4000725       18 ...ation.CultureInfo  0 instance aaef947d00000000 m_CurrentUICulture
79b9f5e8  4000726       1c        System.Object  0 instance aaef947d00000000 m_ThreadStartArg
79b9aa2c  4000727       20        System.IntPtr  1 instance 001D9238 DONT_USE_InternalThread
79ba2978  4000728       24         System.Int32  1 instance        2 m_Priority
79ba2978  4000729       28         System.Int32  1 instance        3 m_ManagedThreadId
79b8b71c  400072a      18c ...LocalDataStoreMgr  0   shared   static s_LocalDataStoreMgr
    >> Domain:Value  0017fd80:NotInit  <<
79b8e2d8  400072b        c ...alDataStoreHolder  0   shared TLstatic s_LocalDataStore
!do -nofields 0113bf00
Name:        System.String
MethodTable: 79b9f9ac
EEClass:     798d8bb0
Size:        30(0x1e) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      MyThread
!GCRoot 0113bf40
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 7708 OSTHread 1e1c
Scan Thread 4572 OSTHread 11dc
Scan Thread 9876 OSTHread 2694
ESP:101f7ec:Root:  0113bf40(System.Threading.Thread)
ESP:101f7f4:Root:  0113bf40(System.Threading.Thread)
DOMAIN(0017FD80):HANDLE(Strong):9b11cc:Root:  0113bf40(System.Threading.Thread)
Thread t;
t = new Thread(o=>Console.WriteLine(o == Thread.CurrentThread));
t.Start(t);