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

时间:2015-02-27 09:34:13

标签: c# .net multithreading winforms garbage-collection

关注this SO answer,我正在做:

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

我的目标是在关闭带有大量图像/ PictureBox控件的大型WinForms表单后进行垃圾收集运行,以确保我的内存中不再有图像。 (我相信我会遵循the instructions of Jon Skeet)。

我在后台线程中进行操作以尝试让我的UI响应。

我的问题:

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

4 个答案:

答案 0 :(得分:22)

当您执行此操作时,您正在丢弃在后台执行垃圾收集的选项。或者换句话说,无论如何,无论您是从工作线程执行此操作,您的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<object>();
            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开始收集,并注意Label中的值。打开复选框,使其在工作人员上运行并进行比较。使用button1重置两者之间的最大值。并修改分配代码,你可能想用位图做一些事情,无论你做什么来要求这个黑客。

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

答案 1 :(得分:9)

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


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

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

摘要:此缩短了 UI暂停时间,但并未完全避免。我建议这样做,因为没有任何伤害。

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

答案 2 :(得分:5)

直接调用GC通常是bad的事情。 Forms类实现Dispose Pattern,所以为什么不使用它。

答案 3 :(得分:2)

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

但我不确定你为什么打电话给GC.Collect两次。 AFAIK GC.Collect后跟GC.WaitForPendingFinalizers就足够了。

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

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

同时通过GC.Collect强制垃圾收集会破坏GC的内部启发式。它将调整针对您的应用程序内存分配活动优化的集合的细分的阈值限制,方法是调用GC.Collect您正在破坏它。