我是否需要保护Thread对象不受垃圾回收器的影响?那么包含线程运行的函数的对象又如何呢?
考虑一下这个简单的服务器:
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();
}
}我是否应该将我的线程对象包装在某种静态收集中,以防止它们被垃圾收集器清除?
假设,如果Thread对象本身保持活动状态,那么Server对象也会活动,因为线程持有对委托的引用,而委托持有对目标对象的引用。也可能是线程对象本身被收集,但实际的线程继续运行。现在可以收集Server对象了。然后,如果它试图访问它的字段,会发生什么呢?
垃圾回收有时会让我头晕目眩。我很高兴我通常不需要去想它。
考虑到这里的潜在陷阱,我相信垃圾收集器足够聪明,不会在线程本身仍在执行时收集线程对象,但我找不到任何文档这么说。Reflector在这里没有什么帮助,因为Thread类的大部分都是在MethodImplOptions.InternalCall函数中实现的,这并不奇怪。而且我不愿意在我的旧的SSCLI副本中寻找答案(这既是因为它很痛苦,也因为它不是一个确定的答案)。
发布于 2011-01-18 14:14:25
这很简单。真正的执行线程不是thread对象。程序是在真正的Windows线程中执行的,不管你的.NET垃圾收集器对你的线程对象做了什么,这些线程都会保持活动状态。所以它对你来说是安全的;如果你只想让程序继续运行,你不需要关心Thread对象。
还要注意,您的线程在运行时不会被收集,因为它们实际上属于应用程序的“根”。(根-垃圾收集器如何知道什么是活的。)
更多细节:托管线程对象可以通过Thread.CurrentThread访问-这类似于全局静态变量,但这些变量不会被收集。正如我之前所写的:任何启动的托管线程现在执行任何代码(甚至在.NET之外),都不会丢失它的thread对象,因为它牢固地连接到“根”。
发布于 2011-01-18 14:31:15
只要你的线程正在执行,它就不会被收集。
发布于 2011-01-18 17:05:25
thread constructor中的start参数(在本例中是server.Serve )是一个您已经知道的委托。
假设,如果线程对象本身保持活动状态,则服务器对象也将活动,因为线程持有对委托的引用,该委托持有对目标对象的引用
这是Jon Skeet所写的C# in Depth对委托目标生命周期的描述
值得注意的是,如果委托实例本身不能被收集,则委托实例将阻止其目标被垃圾收集。
所以,是的,只要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); //this is a demo so its okay
}
});
thread.Name = "MyThread";
thread.Start();
}
}这是输出。(添加threadID后)
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“以确保它是通过gen2实现的gen0。
但是,您关于终结器的观点值得注意。所以我添加了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)正如Production Debugging for .NET Framework Applications所说,它是根。
请注意!GCRoot是在启动后运行的。在它启动之前,它没有手柄(强)。
如果输出包含句柄(强),则找到强引用。这意味着对象是根对象,不能被垃圾回收。其他参考类型可以在附录中找到。
有关托管线程如何映射到OS线程(以及如何使用SOS扩展进行确认)的更多信息,请参见Yun Jin的this article。
https://stackoverflow.com/questions/4720991
复制相似问题