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

Uwe*_*eim 17 .net c# multithreading garbage-collection winforms

按照这个SO答案,我正在做:

ThreadPool.QueueUserWorkItem(
    delegate
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    });
Run Code Online (Sandbox Code Playgroud)

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

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

我的问题:

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

Han*_*ant 23

执行此操作时,您将丢弃在后台执行垃圾收集的选项.或者换句话说,无论如何,无论您是从工作线程执行此操作,您的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;
    }
Run Code Online (Sandbox Code Playgroud)

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

    private void button1_Click(object sender, EventArgs e) {
        maxtick = -1;
    }
Run Code Online (Sandbox Code Playgroud)

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

    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();
                }));
            }
        });
    }
Run Code Online (Sandbox Code Playgroud)

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

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


usr*_*usr 9

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


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

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

摘要:这减少了 UI暂停时间,但并未完全避免.我建议这样做,因为没有伤害.

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


小智 5

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

  • 处置对象不会释放它们使用的托管内存.Dispose将释放非托管资源(可能包括非托管内存).垃圾收集器负责管理内存,并在此过程中还处理不再使用的未处置对象. (3认同)
  • 谢谢,我确实认为我知道这一点.我的问题更像是"如果我必须这样做,那么如何?". (2认同)