可以在后台线程中运行GC.Collect吗?

在这个SO答案之后 ,我正在做:

ThreadPool.QueueUserWorkItem( delegate { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }); 

我的目标是在关闭一个包含大量图像/ PictureBox控件的大型WinForms表单后进行垃圾收集运行,以确保我的内存中不再有图像。 (我确实相信我遵循Jon Skeet的指示 )。

我在后台线程中这样做是为了尝试让我的UI响应。

我的问题:

它是否为我在后台线程中进行垃圾收集带来了什么好处? 或者它实际上使我的应用程序更慢/挂起更长?

执行此操作时,您将丢弃在后台执行垃圾收集的选项。 换句话说,无论如何,无论您是从工作线程执行此操作,您的UI线程都会被挂起。 唯一可行的方法是GC.WaitForPendingFinalizers()花费大量时间。 它实际上并不是你应该等待的东西,没有任何意义,如果它需要的只是眨眼之间,那么你在代码中隐藏了相当严重的错误。

另一个重要的问题是Windows的工作站版本为拥有前景窗口的任何线程提供了更大的量子。 换句话说,允许比后台线程更长时间地刻录核心。 一个简单的黑客攻击,使Windows对用户更敏感。

太多的运动部件,测试你的理论确实是最好的,所以你可以确定在一个工人身上运行一个集合实际上就是你要领先的东西。 测量UI线程暂停非常简单,您可以使用Timer来执行此操作。 当线程被挂起时,它的Tick事件无法运行。 启动一个新的Winforms项目,在表单上放置一个Timer,将其Interval设置为1并将Enabled设置为True,添加一个Label并使用此代码来测量延迟:

  int prevtick = 0; int maxtick = -1; private void timer1_Tick(object sender, EventArgs e) { int tick = Environment.TickCount; if (prevtick > 0) { int thistick = tick - prevtick; if (thistick > maxtick) { maxtick = thistick; label1.Text = maxtick.ToString(); } } prevtick = tick; } 

运行你的程序,你应该看到标签中的16。 如果你得到的少,那么你应该修理你的机器,而不是任何影响这个测试的东西。 添加按钮以重置测量:

  private void button1_Click(object sender, EventArgs e) { maxtick = -1; } 

添加一个复选框和另一个按钮。 我们将让它执行实际的收集:

  private void button2_Click(object sender, EventArgs e) { var useworker = checkBox1.Checked; System.Threading.ThreadPool.QueueUserWorkItem((_) => { var lst = new List(); for (int ix = 0; ix < 500 * 1024 * 1024 / (IntPtr.Size * 3); ++ix) { lst.Add(new object()); } lst.Clear(); if (useworker) { GC.Collect(); GC.WaitForPendingFinalizers(); } else { this.BeginInvoke(new Action(() => { GC.Collect(); GC.WaitForPendingFinalizers(); })); } }); } 

玩这个,点击button2开始收集,并注意标签中的值。 打开复选框,使其在工作人员上运行并进行比较。 使用button1重置两者之间的最大值。 并修改分配代码,你可能想用位图做一些事情,无论你做什么来要求这个hack。

我所看到的:〜在UI线程上执行收集时延迟220毫秒,在工作人员上运行时延迟约340毫秒。 显然,这根本不是一种改进。 从我坐的地方,你的理论已经死在了水中。 请自己尝试一下,我只有一个数据点。 请注意,在服务器版本的Windows或.config文件中的上看起来会有所不同。 你可以玩的其他东西。

更新:这个答案的推理似乎很合理,但Hans Passant的答案显示结论并不成立。 不要根据这个答案得出结论。


这是一个好主意。 所有CLR GC算法至少暂停一次每个线程,但暂停时间小于总GC时间。 对GC.Collect的调用只需要GC总时间。 它具有任何GC循环可能的最大延迟。 这就是为什么不在UI线程上调用它是个好主意。

您的UI线程将在GC期间至少暂停一次但不会在整个持续时间内暂停。 这取决于CLR版本和GC设置将持续多长时间和多少暂停。

摘要:这可以减少 UI暂停时间,但不能完全避免它。 我建议这样做,因为没有伤害。

或者,处置所有非托管资源。 这个问题似乎假设一种不可能或太繁重的情况。

直接调用GC通常是一件坏事。 Forms类实现了Dispose Pattern,所以为什么不使用它。

在BackGround线程中调用GC.Collect没有错。 事实上,它根本没有任何区别。 这只是一个线索; 而已。

但是我不确定你为什么要两次调用GC.Collect 。 AFAIK GC.Collect后跟GC.WaitForPendingFinalizers就足够了。

通过在后台线程中强制GC,您可以使UI响应,但它将消耗与使用UI线程时相同的CPU资源。 如果保持UI响应是目标,是的,你可以。

也就是说,作为一般规则,您不要在生产代码中调用GC.Collect 。 你为什么要这样做? 如果关闭大型表单并且其中的所有对象都有资格进行收集,则Next GC将收集它。 通过立即收集它会带来什么好处?

还通过GC强制垃圾收集GC.Collect破坏了GC的内部启发式。 它将通过调用GC.Collect来调整针对应用程序内存分配活动优化的集合的段的阈值限制。收集您正在破坏它。