Утечки памяти на основе событий C # - PullRequest
13 голосов
/ 20 ноября 2010

У меня есть приложение, которое имеет некоторые утечки памяти из-за того, что события не отсоединяются до того, как для ссылки на объект установлено значение null Приложение довольно большое, и трудно найти утечки памяти, глядя на код. Я хочу использовать sos.dll, чтобы найти имена методов, которые являются источником утечек, но я застреваю. Я создал тестовый проект для демонстрации проблемы.

Здесь у меня есть 2 класса, один с событием, и слушает это событие, как показано ниже

namespace MemoryLeak
{
    class Program
    {
        static void Main(string[] args)
        {
            TestMemoryLeak testMemoryLeak = new TestMemoryLeak();

            while (!Console.ReadKey().Key.Equals('q'))
            {
            }
        }
    }

    class TestMemoryLeak
    {
        public event EventHandler AnEvent;

        internal TestMemoryLeak()
        {
            AnEventListener leak = new AnEventListener();
            this.AnEvent += (s, e) => leak.OnLeak();
            AnEvent(this, EventArgs.Empty);
        }

    }

    class AnEventListener
    {
        public void OnLeak()
        {
            Console.WriteLine("Leak Event");
        }
    }
}

врываюсь в код, а в промежуточном окне набираю

.load sos.dll

затем я использую! Dumpheap, чтобы получить объекты в куче типа AnEventListener

!dumpheap -type MemoryLeak.AnEventListener

и я получаю следующее

PDB symbol for mscorwks.dll not loaded
 Address       MT     Size
01e19254 0040348c       12     
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
0040348c        1           12 MemoryLeak.AnEventListener
Total 1 objects

Я использую! Gcroot, чтобы выяснить, почему объект не подвергается сборке мусора

!gcroot 01e19254

и получите следующее

!gcroot 01e19254
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Error during command: Warning. Extension is using a callback which Visual Studio 
does not implement.

Scan Thread 5208 OSTHread 1458
ESP:2ef3cc:Root:01e19230(MemoryLeak.TestMemoryLeak)->
01e19260(System.EventHandler)->
01e19248(MemoryLeak.TestMemoryLeak+<>c__DisplayClass1)->
01e19254(MemoryLeak.AnEventListener)
Scan Thread 7376 OSTHread 1cd0

Теперь я вижу обработчик событий, который является источником утечки. Я использую! Сделать, чтобы посмотреть на поля обработчика событий и получаем

!do 01e19260
Name: System.EventHandler
MethodTable: 65129dc0
EEClass: 64ec39d0
Size: 32(0x20) bytes
   (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
65130770  40000ff        4        System.Object  0 instance 01e19248 _target
6512ffc8  4000100        8 ...ection.MethodBase  0 instance 00000000 _methodBase
6513341c  4000101        c        System.IntPtr  1 instance 0040C060 _methodPtr
6513341c  4000102       10        System.IntPtr  1 instance 00000000 _methodPtrAux
65130770  400010c       14        System.Object  0 instance 00000000 _invocationList
6513341c  400010d       18        System.IntPtr  1 instance 00000000 _invocationCount

Итак, теперь я вижу указатель на метод, который не отключается

0040C060 _methodPtr

но как мне получить название этого метода?

Ответы [ 5 ]

4 голосов
/ 20 ноября 2010

События хитры, потому что, когда А подписывается на B, оба заканчивают тем, что держат ссылку друг на друга. В вашем примере это не проблема, поскольку нет утечки (A создал B и является единственным объектом, который содержит ссылку на B, поэтому A и B умрут, когда A умрет).

Для реальных проблем, связанных с событиями, необходимо решить концепцию «слабых событий». К сожалению, единственный способ получить 100% работающие слабые события - это поддержка со стороны CLR. Похоже, что Microsoft не заинтересована в предоставлении такой поддержки.

Я рекомендую вам погуглить "слабые события в C #" и начать читать. Вы найдете много разных подходов к решению проблемы, но вы должны знать об их ограничениях. Там нет 100% решение.

1 голос
/ 21 ноября 2010

Расширяя идею IDisposable, предложенную @Max Malygin:

Приведенный ниже код показывает, как проверять выдающиеся обработчики события.

В классе есть событие Tick, которое срабатывает один раз в секунду. Когда вызывается Dispose, код перечисляет обработчики в списке вызовов (если они есть) и выводит имя класса и метода, которые все еще подписаны на событие.

Программа создает экземпляр объекта, присоединяет обработчик события, который записывает «галочку» каждый раз, когда происходит событие, а затем спит в течение 5 секунд. Затем он удаляет объект без отмены подписки на обработчик событий.

using System;
using System.Diagnostics;
using System.Threading;

namespace testo
{
    public class MyEventThing : IDisposable
    {
        public event EventHandler Tick;
        private Timer t;

        public MyEventThing()
        {
            t = new Timer((s) => { OnTick(new EventArgs()); }, null, 1000, 1000);
        }

        protected void OnTick(EventArgs e)
        {
            if (Tick != null)
            {
                Tick(this, e);
            }
        }

        ~MyEventThing()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool disposed = false;
        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    t.Dispose();
                    // Check to see if there are any outstanding event handlers
                    CheckHandlers();
                }

                disposed = true;
            }
        }

        private void CheckHandlers()
        {
            if (Tick != null)
            {
                Console.WriteLine("Handlers still subscribed:");
                foreach (var handler in Tick.GetInvocationList())
                {
                    Console.WriteLine("{0}.{1}", handler.Method.DeclaringType, handler.Method.Name);
                }
            }
        }

    }

    class Program
    {
        static public long Time(Action proc)
        {
            Stopwatch sw = Stopwatch.StartNew();
            proc();
            return sw.ElapsedMilliseconds;
        }

        static int Main(string [] args)
        {
            DoIt();
            Console.WriteLine();
            Console.Write("Press Enter:");
            Console.ReadLine();
            return 0;
        }

        static void DoIt()
        {
            MyEventThing thing = new MyEventThing();
            thing.Tick += new EventHandler(thing_Tick);
            Thread.Sleep(5000);
            thing.Dispose();
        }

        static void thing_Tick(object sender, EventArgs e)
        {
            Console.WriteLine("tick");
        }
    }
}

Вывод:

Handlers still subscribed:
testo.Program.thing_Tick
1 голос
/ 20 ноября 2010

Я бы порекомендовал использовать профилировщик памяти .NET, чтобы разобраться в этом вопросе, есть несколько из них - лично я использовал Red Gate ANTS профилировщик памяти в прошлом, который имеет14-дневная бесплатная пробная версия:

http://www.red -gate.com / products / ants_memory_profiler / walkthrough.htm

1 голос
/ 20 ноября 2010

А как насчет реализации старого доброго IDisposable?

        class TestMemoryLeak : IDisposable
        {
              public event EventHandler AnEvent;
              private bool disposed = false;

           internal TestMemoryLeak()
           {
                 AnEventListener leak = new AnEventListener();
                 this.AnEvent += (s, e) => leak.OnLeak();
                AnEvent(this, EventArgs.Empty);
           }

           protected virtual void Dispose(bool disposing)
           {
              if (!disposed)
               {
                 if (disposing)
                 {
                      this.AnEvent -= (s, e) => leak.OnLeak();
                 }
                 this.disposed = true;
                }

            }

           public void Dispose() 
           {
                this.Dispose(true);
                GC.SupressFinalize(this);
           }

    }
0 голосов
/ 05 августа 2014

Вы можете попробовать вот так на WinDbg

  1. Dump Target Obj для получения таблицы методов: ! Dumpobj 01e19248
  2. Dump Method Table, чтобы найти в ней 0040C060: ! Dumpmt -md 0ced1910
  3. Если совпадений нет, дамп памяти, начинающийся с адреса _methodPtr: ! U 0040C060
  4. Найти JMPили MOVE и введите свой адрес, например: ! u 0cf54930

Посетите здесь для получения более подробной информации: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...