Обрабатывает утечку (тип события) в приложении .NET.

У меня есть приложение Windows Forms, написанное на .NET 4.0. Недавно, выполняя некоторые тесты, я заметил, что есть проблема с ручками. В таблице ниже показаны результаты:

введите здесь описание изображения

Как вы можете видеть, единственным типом дескриптора, который увеличивается, является Event.

Итак, мой вопрос: возможно ли, что описанная проблема вызвана приложением Windows Forms? Я имею в виду, что я не синхронизирую потоки с помощью AutoResetEvent или ManualResetEvent. Я использую потоки, но то, что видно из таблицы выше, показывает количество дескрипторов потоков. Итак, я предполагаю, что они хорошо управляются CLR?

Может ли это быть вызвано какими-либо сторонними компонентами, которые я также использую в своем приложении?

Если что-то непонятно, я постараюсь ответить на ваши вопросы. Спасибо за помощь!


person rwasik    schedule 16.02.2014    source источник
comment
Если вы используете Process Explorer, вы можете увидеть список всех дескрипторов событий в нижней панели (на моем компьютере это сочетание клавиш Ctrl+L). Их имена могут помочь вам определить, где они созданы. Кроме того, вы также можете использовать windbg для выяснения их происхождения.   -  person Groo    schedule 17.02.2014


Ответы (2)


События являются основным источником утечек памяти в .Net, а AutoResetEvent и ManualResetEvent имеют очень плохие имена. Они не являются причиной.

Когда вы видите что-то вроде этого:

myForm.OnClicked += Form_ClickHandler

Именно о таком событии идет речь. Когда вы регистрируете обработчик событий, источник события (например, OnClicked) сохраняет ссылку на обработчик. Если вы создаете и регистрируете новые обработчики, вы ДОЛЖНЫ отменить регистрацию события (например, myForm.OnClicked -= Form_ClickHandler), иначе использование вашей памяти будет продолжать расти.

Для получения дополнительной информации:

person Iain Ballard    schedule 16.02.2014
comment
Я сомневаюсь, что таблица OP относится к управляемым событиям, но, скорее всего, к обработчикам системных событий (т.е. созданным с использованием CreateEvent). Имя столбца говорит о типе дескриптора и, вероятно, выводится из внешнего приложения (обозреватель процессов или что-то в этом роде). - person Groo; 17.02.2014
comment
Хороший вопрос Гру. @rwasik - не могли бы вы уточнить, что представляет собой таблица и / или как вы ее создали? Знаете ли вы, совершаете ли вы неуправляемые звонки? - person Iain Ballard; 17.02.2014
comment
Таблица представляет дескрипторы, что означает (согласно определению) количество дескрипторов объектов в таблице объектов процесса. Я использовал Process Explorer, чтобы получить его. После просмотра свойств типа события вы можете увидеть описание, в котором говорится: «Объект синхронизации». Более того, есть раздел «Информация о событии», содержащий две информации: Тип — Синхронизация, Сигнал — Ложь. Вот почему я думаю, что это связано с потоками, а не с обработчиками событий. И нет, я не делаю никаких неуправляемых звонков. - person rwasik; 17.02.2014
comment
@rwasik: можешь попробовать делать GC.Collect(4, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); Периодически (то есть каждые 10 минут). Если это остановит рост счетчика дескрипторов событий, возможно, вы столкнулись с известной ошибкой .Net. - person Iain Ballard; 17.02.2014
comment
Я попробовал ваш подход. После 16 часов тестирования предложенные вами функции GC вызывались 10 раз. Не помогло, т.к. количество обработчиков событий выросло с 264 до 9400. Значит ли это, что какая-то проблема во внешней dll? И что вы подразумеваете под известной ошибкой .Net? - person rwasik; 19.02.2014
comment
Если вы быстро открываете и закрываете много потоков в приложении с большим объемом памяти, может возникнуть длительная задержка при сборе ресурсов потока. Не похоже, чтобы это происходило. Можете ли вы перечислить внешние DLL, на которые вы ссылаетесь? - person Iain Ballard; 19.02.2014
comment
Извините, я не могу. Но после этих тестов возможно ли, что что-то не так в приложении .net? - person rwasik; 20.02.2014
comment
Я не могу сказать без дополнительной информации. Возможно, попробуйте отключить части вашей системы и посмотреть, когда проблема исчезнет. Это может быть сложно для тесно связанного кода, но это один из лучших способов поиска сложных ошибок. - person Iain Ballard; 20.02.2014

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

Ответ заключается в том, что .NET создает объекты ядра Event для различных примитивов потоковой передачи, когда возникает конкуренция. Примечательно, что я сделал тестовое приложение, которое может показать, что они создаются при использовании оператора «lock», хотя, по-видимому, любой из примитивов потоковой передачи Slim будет выполнять подобное ленивое создание.

Важно отметить, что дескрипторы НЕ являются утечками, хотя увеличение числа может указывать на утечку в другом месте вашего кода. Дескрипторы будут освобождены, когда сборщик мусора соберет объект, который их создал (например, объект, указанный в операторе блокировки).

Я вставил свой тестовый код ниже, который продемонстрирует утечку в небольшом масштабе (около 100 просочившихся дескрипторов событий на моей тестовой машине — ваш пробег может отличаться).

Несколько конкретных интересных моментов:

  • После очистки списка и запуска GC.Collect() все созданные дескрипторы будут очищены.

  • Установка для ThreadCount значения 1 предотвратит создание любых дескрипторов событий.

  • Аналогично, комментирование оператора lock не приведет к созданию дескрипторов.

  • Удаление ThreadCount из вычисления index (строка 72) резко уменьшит конкуренцию и, таким образом, предотвратит создание почти всех дескрипторов.

  • Независимо от того, как долго вы позволяете ему работать, он никогда не создаст более 200 дескрипторов (кажется, что .NET по какой-то причине создает 2 для каждого объекта).

using System.Collections.Generic;
using System.Threading;

namespace Dummy.Net
{
    public static class Program
    {
        private static readonly int ObjectCount = 100;
        private static readonly int ThreadCount = System.Environment.ProcessorCount - 1;

        private static readonly List<object> _objects = new List<object>(ObjectCount);
        private static readonly List<Thread> _threads = new List<Thread>(ThreadCount);

        private static int _currentIndex = 0;
        private static volatile bool _finished = false;
        private static readonly ManualResetEventSlim _ready = new ManualResetEventSlim(false, 1024);

        public static void Main(string[] args)
        {
            for (int i = 0; i < ObjectCount; ++i)
            {
                _objects.Add(new object());
            }

            for (int i = 0; i < ThreadCount; ++i)
            {
                var thread = new Thread(ThreadMain);
                thread.Name = $"Thread {i}";
                thread.Start();
                _threads.Add(thread);
            }

            System.Console.WriteLine("Ready.");

            Thread.Sleep(10000);

            _ready.Set();
            System.Console.WriteLine("Started.");

            Thread.Sleep(10000);

            _finished = true;

            foreach (var thread in _threads)
            {
                thread.Join();
            }

            System.Console.WriteLine("Finished.");

            Thread.Sleep(3000);

            System.Console.WriteLine("Collecting.");

            _objects.Clear();
            System.GC.Collect();

            Thread.Sleep(3000);

            System.Console.WriteLine("Collected.");

            Thread.Sleep(3000);
        }

        private static void ThreadMain()
        {
            _ready.Wait();

            while (!_finished)
            {
                int rawIndex = Interlocked.Increment(ref _currentIndex);
                int index = (rawIndex / ThreadCount) % ObjectCount;
                bool sleep = rawIndex % ThreadCount == 0;

                if (!sleep)
                {
                    Thread.Sleep(10);
                }

                object obj = _objects[index];
                lock (obj)
                {
                    if (sleep)
                    {
                        Thread.Sleep(250);
                    }
                }
            }
        }
    } 
}
person DragonFireCK    schedule 24.04.2020