在您的情况下,线程构造函数中的start参数(即server.Serve)是一个委托,您已经知道了。
假设Thread对象本身保持活动状态,则Server对象将保持活动状态,因为线程持有对委托的引用,该委托持有对目标对象的引用
这是Jon Skeet的C#深入研究中关于委托目标生命周期的内容
值得注意的是,如果委托实例本身无法被收集,委托实例将防止其目标被垃圾回收。
所以,只要var thread在作用域内,server就不会被回收。但是,如果在调用thread.start之前var thread超出作用域(且没有其他引用),那么它可以被回收。
现在的问题是,当Thread.Start()被调用并且在server.Serve完成之前该线程已经超出作用域时,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);
}
});
thread.Name = "MyThread";
thread.Start();
}
}
这是输出。(添加线程ID后)
test MyThread 3 1
test is over
test MyThread 3 2
test MyThread 3 3
test MyThread 3 4
test MyThread 3 5
所以我调用了一个创建线程并启动它的方法。该方法结束后,
var thread
就超出了其作用域。但即使我已经引发了GC并且线程变量超出了作用域,线程仍然在运行。
因此,只要在变量超出作用域之前启动线程,一切都会按预期工作。
更新
为了澄清
GC.Collect
强制立即回收所有代的垃圾。可以回收的任何内容都将被回收。这不是评论中所暗示的“仅仅是意图的表达”。(如果是这样的话,就没有理由警告不要调用它)但是,我添加了参数“2”以确保它是gen0到gen2。
你提到的关于终结器的观点值得注意。因此,我添加了
GC.WaitForPendingFinalizers
“...暂停当前线程,直到处理终结器队列的线程清空该队列。” 这意味着需要处理的任何终结器都已被处理。 该示例的重点在于只要启动线程,它就会一直运行,直到被中止或完成,并且 GC 不会因为 var thread 已经超出其作用域而中止线程。此外,Al Kepp正确指出,一旦启动线程,System.Threading.Thread将被根化。 您可以使用SOS扩展程序找出这一点。
!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
这是名为MyThread的线程吗?
!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)
如 .NET Framework应用程序的生产调试所述,它是根源。
请注意,在启动之前运行了!GCRoot。在启动之前,它没有HANDLE(Strong)。
如果输出包含HANDLE(Strong),则找到了强引用。这意味着对象已经根据,并且无法进行垃圾回收。其他引用类型可以在附录中找到。
有关管理线程如何映射到操作系统线程(以及如何使用SOS扩展确认它)的更多信息,请参见Yun Jin的本文。