【问题标题】:C# Thread object lifetimeC# 线程对象生命周期
【发布时间】:2011-04-11 13:54:18
【问题描述】:

假设我有如下代码:

int Main()
{
    if (true)
    {
       new Thread(()=>
          {
              doSomeLengthyOperation();
          }).Start();
    }
    while (true)
    {
       //do nothing
    }
}

有 2 个线程,我将调用 Main 线程,即执行 Main() 函数的线程,并将在“if”测试中新建的线程称为线程 A。

我的问题是,线程 A 什么时候被销毁? doSomeLenghtyOperation() 能否完成?

由于没有指向线程A的引用,是否将其标记为垃圾回收的候选对象:

  1. “new Thread().Start()”语句本身完成后立即?
  2. 在“if(true)”范围退出后立即?
  3. doSomeLengthOperation() 运行完成后?
  4. 从不?

我看到的所有例子都是 Main() 持有引用,然后主线程在退出之前等待加入线程 A。我很好奇上面代码的生命周期是什么。

提前致谢!

【问题讨论】:

  • 顺便说一句,if (true) 在这里绝对没有必要。您可以只使用大括号来创建范围。
  • 是的,我知道。但是描述“未命名的范围”有点困难,所以我决定使用 if (true) 来让事情更清楚。

标签: c# multithreading object-lifetime


【解决方案1】:

“线程”一词在这里可能意味着几件事:

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

一旦 Start() 方法完成,线程对象将成为 GC 的候选对象,因为不再有对它的引用。

在 doSomeLengthyOperation() 运行时,托管线程将保持活动状态。

引用 Microsoft MVP James Kovacs 的 article

托管线程的生命周期是 独立于 Thread 对象 创造它,这是一件非常好的事情 你不希望GC 终止一个仍在运行的线程 工作只是因为你失去了一切 对相关线程的引用 目的。所以 GC 正在收集 线程对象,但不是实际的 托管线程。

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

理论上,操作系统线程与托管线程没有一对一的关系。来自MSDN

...复杂的主机可以使用 CLR Hosting API 针对同一操作系统线程调度多个托管线程,或在不同操作系统线程之间移动托管线程。

然而,在实践中,CLR 线程 maps directly to a Windows thread today

【讨论】:

    【解决方案2】:

    Thread 对象一旦不再被使用,即在调用 Start 方法后立即有资格进行垃圾回收。 (但不会立即收集,因为垃圾收集器会在特定时间运行。)

    然而,实际的线程并不依赖于Thread 对象,即使Thread 对象被收集,它也会继续运行。

    如果 main 方法退出时线程仍在运行,则应用程序在线程完成之前不会结束,除非您已将线程标记为后台线程。

    【讨论】:

    • “线程对象将尽快进行垃圾收集”。这不是真的。 GC 不是确定性的。你必须说“它将成为垃圾收集的候选者”。除非达到特定阈值,否则 GC 不会启动。
    • 如果 Thread 对象可以很快被收集,那么它的终结器也可以运行。但是,这不会与仍在运行的线程发生冲突吗?
    • @Aliostad:就是这个意思,但我会说得更清楚,以免引起任何误解。
    • 对此我仍然不满意。如果线程对象被 GCed,那么如果我们在 LongRunning 任务中使用线程对象本身的属性会发生什么?例如使用 Thread.CurrentThread.Name。我认为很有可能 Thread.CurrentThread 将其保留在内存中。
    • 我很确定这个答案是错误的:线程 A not 符合收集条件,因为在它自己的执行上下文中,仍然有对它的引用 Thread.Current -并且可能还有其他参考资料。线程运行时不能对线程对象进行 GC。这里还有一个额外的问题:它确实取决于究竟是什么被认为是 GC 根,特别是 Thread.Current 是如何计算的——就像我假设 Thread.Current 以某种方式“存储”一个强引用而不是拥有某种弱引用缓存。
    【解决方案3】:

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

    更新

    我相信它不会被垃圾收集,因为 Thread.CurrentThread 通过引用将它保存在内存中。

    【讨论】:

    • Thread 对象很可能会被收集,但这不会影响实际线程。
    • 这个答案似乎自相矛盾。
    【解决方案4】:

    线程 A 什么时候销毁?

    doSomeLengthyOperation 结束时。

    doSomeLenghtyOperation() 是否能够运行完成

    是的,即使主线程存在,因为它不是后台线程。如果在主线程存在时启动线程之前将IsBackground属性设置为true,则该线程也将停止。

    【讨论】:

    • 来自 MSDN 文档:“启动线程后,不必保留对 Thread 对象的引用。线程继续执行,直到线程过程完成。” “后台线程与前台线程相同,只是后台线程不会阻止进程终止。一旦属于进程的所有前台线程都已终止,公共语言运行时将结束该进程。任何剩余的后台线程都将停止并且不会完成。”
    • -1:您必须区分 Thread 对象和实际线程,因为 OP 明确询问 Thread 对象是否被垃圾收集。没有什么可以让Thread 对象保持活动状态仅仅因为实际线程仍在运行。
    • @Guffa - 我找不到任何权威来源,但我相信正在运行的线程被视为 GC 根。此外,由于每个托管线程不对应于非托管计数器部分,因此托管线程对象很可能被视为其自身的根对象(除非 CLR 使用其他一些内部数据结构来表示托管线程,这是很有可能的)。
    猜你喜欢
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 2022-06-12
    • 2021-06-04
    • 1970-01-01
    相关资源
    最近更新 更多