C#线程对象的生命周期

21

假设我有以下代码:

int Main()
{
    if (true)
    {
       new Thread(()=>
          {
              doSomeLengthyOperation();
          }).Start();
    }
    while (true)
    {
       //do nothing
    }
}
有2个线程,我将称执行Main()函数的线程为主线程,if测试中被new出来的线程为线程A。我的问题是,线程A什么时候被销毁?doSomeLenghtyOperation()能否完成运行?
由于没有引用指向线程A,它会被标记为垃圾回收的候选对象:
1.在“new Thread().Start()”语句本身完成后立即? 2.在退出“if(true)”作用域后立即? 3.doSomeLengthOperation()运行完成后立即? 4.从不?
我看到的所有示例都是Main()持有引用,然后Main线程等待与线程A合并后退出。我想知道上面代码的生命周期。
先感谢您!

1
顺便提一下,在这里使用 if (true) 是完全不必要的。你可以只使用大括号来创建一个作用域。 - VladV
3
我知道。但是,“未命名的范围”有点难以描述,所以我决定使用 if (true) 来使事情更清晰。 - jonathan_ou
4个回答

16

在这里,“线程”一词可能意味着以下几种情况:

  • new Thread()创建的System.Threading.Thread对象,
  • 管理线程(CLR线程),
  • 非托管线程(操作系统线程)。

当Start()方法完成后,Thread对象将成为GC的候选对象,因为没有对它的引用。

只要doSomeLengthyOperation()运行,托管线程就会保持活动状态。

引用Microsoft MVP James Kovacs的文章

托管线程的生命周期独立于创建它的Thread对象,这是一件非常好的事情,因为如果您失去与相关联的Thread对象的所有引用,您不希望GC终止正在执行工作的线程。因此,GC正在收集Thread对象,但不是实际的托管线程。

如果您想自己尝试,该文章还包含一些有用的代码示例。

MSDN中可以得知,理论上,操作系统线程与托管线程之间没有一对一的关系:

...一个复杂的宿主可以使用CLR Hosting API来在同一个操作系统线程上安排许多托管线程,或将托管线程移动到不同的操作系统线程中。

但是,在实践中,CLR线程今天直接映射到Windows线程


15
Thread对象在调用Start方法后不再被使用时即可被垃圾回收,但实际上垃圾收集器是定期运行的,因此不会立即回收。Thread对象并不影响线程的运行,即使该对象被回收,线程仍将继续运行。

如果主方法退出时线程仍在运行,则除非将线程标记为后台线程,否则应用程序将不会结束,直到线程完成。


3
“Thread对象将在尽快进行垃圾收集。”这是不正确的。垃圾回收不是确定性的。你必须说“它将成为垃圾回收的候选对象”。只有达到一定的阈值,垃圾回收才会启动。 - Aliostad
1
@Aliostad:这就是它的意思,但为了避免任何误解,我将使其更加清晰。 - Guffa
5
我对这仍然不满意。如果线程对象被垃圾回收了,那么如果我们在 LongRunning 任务中使用线程对象本身的属性会发生什么呢?例如使用 Thread.CurrentThread.Name。我认为 Thread.CurrentThread 很可能会保留它在内存中。 - Aliostad
2
我非常确定这个答案是错误的:线程A 可以被收集,因为在其自身执行的上下文中仍然存在对它的引用Thread.Current - 还可能有其他引用。在线程运行时,线程对象无法被GC回收。这里还有一个额外的问题:它确实取决于什么被认为是GC根,特别是如何计算Thread.Current - 也就是说,我假设Thread.Current以某种方式“存储”强引用,而不是具有某种弱引用缓存。 - Eamon Nerbonne
@Guffa:我使用ReferenceEquals进行了检查,最初至少在线程内部,Thread.Current返回由new Thread语句创建的确切对象。这并不能保证它总是这样,但我敢打赌它会是这样的:为什么不呢? - Eamon Nerbonne
显示剩余4条评论

10

线程A何时被销毁?

doSomeLengthyOperation执行完毕时。

doSomeLenghtyOperation()能够顺利执行完成吗?

可以,即使主线程退出也可以,因为它不是后台线程。如果在启动线程之前将IsBackground属性设置为true,则每当主线程退出时,此线程也将停止。


4
来自 MSDN 文档:"一旦你启动了线程,就没必要保留对线程对象的引用。线程会一直执行,直到线程过程完成。" "后台线程和前台线程是相同的,唯一的区别在于后台线程不会防止进程终止。当属于进程的所有前台线程都终止时,公共语言运行时结束该进程。任何剩余的后台线程都会被停止并且无法完成。" - weismat
1
你必须区分Thread对象和实际线程之间的差别,因为OP明确询问Thread对象是否被垃圾回收。仅仅因为实际线程仍在运行并没有保持Thread对象存活。 - Guffa
@Guffa - 我找不到任何权威来源,但我认为正在运行的线程被视为GC根。此外,由于每个托管线程都没有对应的非托管部分,因此托管线程对象本身可能被视为根对象(除非CLR使用其他内部数据结构来表示托管线程,这是很有可能的)。 - VinayC

0

这是一个非常好的问题!线程肯定会完成,你可以自己尝试。但如果在 while 循环中调用 GC.Collect(),它可能会变得有趣。根据 Richter 的 C# via CLR,它将被垃圾回收。

更新

我认为它不会被垃圾回收,因为 Thread.CurrentThread 通过引用将其保留在内存中。


Thread 对象很可能会被收集,但这并不影响实际的线程。 - Guffa
1
这个答案似乎自相矛盾。 - Peter Ritchie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接