Можно ли запустить GC.Collect в фоновом потоке?

Следуя this SO answer, я делаю:

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

Моя цель - запустить сборку мусора после закрытия большой формы WinForms с большим количеством элементов управления images/PictureBox, чтобы у меня больше не было изображений в памяти. (Я верю, что следую инструкциям Джона Скита ).

Я делаю это в фоновом потоке, чтобы попытаться настроить свой пользовательский интерфейс.

Мой вопрос:

Приносит ли мне какие-либо выгоды, чтобы делать сборку мусора в фоновом потоке? Или это на самом деле делает мое приложение медленнее/дольше?

Ответы

Ответ 1

Вы выбрасываете опцию для сбора мусора, выполняемого на фоне, когда вы это делаете. Или, другими словами, ваш поток пользовательского интерфейса будет приостановлен в любом случае, независимо от того, выполняете ли вы это из рабочего потока. Единственный возможный способ быть впереди - когда GC.WaitForPendingFinalizers() занимает значительное количество времени. На самом деле это не то, чего вы когда-либо ждали, нет смысла, и если это займет больше минуты, тогда вы скрываете довольно серьезные ошибки в своем коде.

Еще одна существенная морщина заключается в том, что версия рабочей станции Windows предоставляет любой поток, которому принадлежит окно переднего плана большего кванта. Другими словами, разрешено сжигать ядро ​​дольше, чем фоновый поток. Простой взлом, чтобы сделать Windows более восприимчивой к пользователю.

Слишком много движущихся частей, на самом деле лучше всего проверить вашу теорию, чтобы вы могли быть уверены, что запуск коллекции на рабочем месте - это то, чем вы вперед. Измерение приостановки подвески пользовательского интерфейса довольно просто, вы можете использовать таймер для этого. Событие Tick не может работать, когда поток приостановлен. Запустите новый проект Winforms, пометьте Timer в форме, установите для Interval значение 1 и Enabled равным True, добавьте ярлык и используйте этот код для измерения задержек:

    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 в ярлыке. Если вы получаете меньше, то вы должны закрепить свою машину, а не иначе, что влияет на этот тест. Добавьте кнопку reset для измерения:

    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();
                }));
            }
        });
    }

Играйте с этим, нажмите кнопку2, чтобы начать сбор и обратите внимание на значение в ярлыке. Установите флажок, чтобы он работал на рабочем месте и сравнивался. Используйте кнопки1 до reset максимально между ними. И измените код выделения, вы, вероятно, захотите что-то сделать с растровыми изображениями, что бы вы ни делали, чтобы потребовать этот хак.

Что я вижу: ~ 220 мс задержка при выполнении коллекции в потоке пользовательского интерфейса, ~ 340 мс задержка при запуске на рабочем столе. Понятно, что это не улучшение вообще. С того места, где я сижу, ваша теория мертва в воде. Пожалуйста, попробуйте сами, у меня есть только один набор данных. Остерегайтесь, что это будет выглядеть совсем по-другому на серверной версии Windows или с помощью <gcServer=true> в файле .config. Что-то еще, с чем вы можете играть.

Ответ 2

Обновление: рассуждения в этом ответе кажутся довольно громкими, но ответ Ханса Пассана ниже показывает, что вывод не выполняется. Не переходите к выводам, основанным на этом ответе.


Это хорошая идея. Все алгоритмы CLR GC приостанавливают каждый поток хотя бы один раз, но паузы меньше, чем общее время GC. Вызов GC.Collect занимает столько же времени, сколько и общее время GC. Он имеет максимальную задержку для любого цикла GC. Вот почему это хорошая идея не называть его в потоке пользовательского интерфейса.

Ваш поток пользовательского интерфейса будет приостановлен во время GC хотя бы один раз, но не на всю продолжительность. Это зависит от версии CLR и настроек GC, как долго и сколько пауз там будет.

Резюме. Это сокращает время паузы в пользовательском интерфейсе, но не полностью исключает его. Я рекомендую сделать это, потому что нет никакого вреда.

В качестве альтернативы, удалите все неуправляемые ресурсы. Этот вопрос, похоже, предполагает ситуацию, когда это невозможно или слишком обременительно.

Ответ 3

Вызов GC напрямую - это bad. Класс Forms реализует Dispose Pattern, так почему бы вам не использовать его.

Ответ 4

Нет ничего плохого в вызове GC.Collect в потоке BackGround. На самом деле это не имеет никакого значения. Это просто поток; что он.

Но я не уверен, почему вы звоните GC.Collect дважды. AFAIK GC.Collect, за которым следует GC.WaitForPendingFinalizers.

Заставляя GC в фоновом потоке, вы можете сделать пользовательский интерфейс отзывчивым, но он будет потреблять те же ресурсы ЦП, как если бы вы использовали поток пользовательского интерфейса. Если поддерживать отзывчивость пользовательского интерфейса является целью, да, вы можете.

Тем не менее, как правило, вы не называете GC.Collect в своем производственном коде. Зачем вам это делать? Если большая форма закрыта, и все объекты внутри нее имеют право на сбор, Next GC соберет ее. Какую пользу вы получите, собирая его немедленно?

Также заставляя сбор мусора через GC.Collect портить GC-эвристику GC. Он отрегулирует пороговый предел сегментов для коллекции, оптимизированной для вашей деятельности по распределению памяти приложения, вызвав GC.Collect, который вы испортили.