6 января 2017 г.

Почему приложение грузит CPU

Суть проблемы.


Ваше запущенное приложение в какой то момент начинает активно грузить CPU, вас зовёт тестер и просит починить это!

Какие обычные действия программистов в таком случае?


  • Просят локализовать, если получается, то решить проблему вопрос времени.
  • Начинается добавление логов, счетчиков проходов и тому подобного. Все отдается тестеру или заказчику с требованием воспроизвести и вернуть лог на анализ. Хорошо если воспроизвести удастся и все станет ясно.
  • Предположить время, когда "все работало" и по изменениями в системе контроля версий искать возможные причины.


Как проще поступить вэтом случае?


Загрузка CPU означает, что какой то поток(и) обработки данных проснулся\запустился, и стал активно выполнять свою работу или иногда просто зациклился. Узнав стек выполнения в момент нагрузки, можно с высокой долей вероятности понять причину такого поведения.

Как же его можно узнать, ведь мы не находимся под отладчиком? Лично я пользуюсь утилитой Process Explorer дающая возможность увидеть список потоков и их стек. Программа установки не требует.

Для демонстрации я запустил свое приложение с именем процесса "Qocr.Application.Wpf.exe", в которое добавил фейковый код бесконечного цикла. Теперь давайте найдём причину загрузки ядра без отладчика. Для этого я иду в ствойства процесса, далее:
  1. Переходим на вкладку Threads и видим, что имеется 1 поток, который грузит на 16% CPU.
  2. Выделяем этот поток и жмем Stack, открылось окно "Stack for thread ID".
  3. В окне видим, что наш поток был создан тут Qocr.Application.Wpf.exe!<>c.b__36_1+0x3a  и в данный момент вызывает GetDirectories из метода InitLanguages().

Продемонстрирую действия выше на изображении со стрелками:




Открыв исходный код программы и перейдя к методу InitLanguages можно увидеть мой фейковый код. Зная эту информацию, а именно место отстановки, можно уже принимать меры.

Код стека (из примера выше) вызывающий бесконечный цикл (Можно проверить):

private void InitLanguages()
{
    new Thread(
        () =>
        {
            while (true)
            {
                var dir = Directory.GetDirectories(@"C:\");
            }
            ;
        }).Start();
}


Ложка дегтя в бочке с медом.


Два момента, которые стоит знать, если решите воспользоваться способом выше:
  1. Потоки созданные CLR (созданные в коде .NET приложения) после останова не продолжают выполнение. В результате чего поток останавливается и остается висеть до перезапуска программы. 
  2. Если стек исполнения не содержит полезной информации, то стоит проделать остановку и просмотр стека несколько раз. Вероятность  наткнуться на место зацикливания очень велика.

3 комментария:

  1. Все работает Спасибо вам !!!!

    ОтветитьУдалить
  2. Спасибо, Сергей, нашел вечный цикл за минуту!

    ОтветитьУдалить
  3. Очень рад что кому то пригодилось

    ОтветитьУдалить