Как события вызывают утечку памяти в C # и как Weak References помогают смягчить это? - PullRequest
27 голосов
/ 08 сентября 2010

Существует два способа (о которых я знаю), чтобы вызвать непреднамеренную утечку памяти в C #:

  1. Не выбрасывать ресурсы, которые реализуют IDisposable
  2. Ссылки и отсылкинеправильная ссылка на события.

Я не совсем понимаю второй пункт.Если исходный объект имеет более длительное время жизни, чем слушатель, и слушателю больше не нужны события, когда на него нет других ссылок, использование обычных событий .NET вызывает утечку памяти: исходный объект хранит объекты слушателя в памяти, котораядолжен быть мусор.

Можете ли вы объяснить, как события могут вызвать утечки памяти с кодом в C #, и как я могу код обойти это с помощью Weak References и без Weak References?

Ответы [ 3 ]

35 голосов
/ 08 сентября 2010

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

Рассмотрим следующие классы:

class Source
{
    public event EventHandler SomeEvent;
}

class Listener
{
    public Listener(Source source)
    {
        // attach an event listner; this adds a reference to the
        // source_SomeEvent method in this instance to the invocation list
        // of SomeEvent in source
        source.SomeEvent += new EventHandler(source_SomeEvent);
    }

    void source_SomeEvent(object sender, EventArgs e)
    {
        // whatever
    }
}

...и затем следующий код:

Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;

Даже если мы присвоим null для listener, он не будет иметь права на сборку мусора, так как newSource все еще содержит ссылку на обработчик события (Listener.source_SomeEvent).Чтобы устранить утечку такого рода, важно всегда отсоединять прослушиватели событий, когда они больше не нужны.

Приведенный выше пример написан для того, чтобы сосредоточиться на проблеме с утечкой.Чтобы исправить этот код, возможно, проще всего будет позволить Listener удерживать ссылку на Source, чтобы впоследствии он мог отсоединить прослушиватель события:

class Listener
{
    private Source _source;
    public Listener(Source source)
    {
        _source = source;
        // attach an event listner; this adds a reference to the
        // source_SomeEvent method in this instance to the invocation list
        // of SomeEvent in source
        _source.SomeEvent += source_SomeEvent;
    }

    void source_SomeEvent(object sender, EventArgs e)
    {
        // whatever
    }

    public void Close()
    {
        if (_source != null)
        {
            // detach event handler
            _source.SomeEvent -= source_SomeEvent;
            _source = null;
        }
    }
}

Затем вызывающий кодможет сигнализировать, что это сделано с помощью объекта, который удалит ссылку, которая Source имеет на «Listener»;

Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;
12 голосов
/ 08 сентября 2010

Прочитайте отличную статью Джона Скита о событиях. Это не настоящая «утечка памяти» в классическом смысле, а скорее неконтролируемая ссылка. Так что всегда помните -= обработчик событий, который вы += в предыдущей точке, и вы должны быть золотым.

2 голосов
/ 08 сентября 2010

Строго говоря, в «песочнице» управляемого проекта .NET нет «утечек памяти»; Есть только ссылки, которые хранятся дольше, чем разработчик счел бы необходимым. Фредрик имеет право на это; когда вы присоединяете обработчик к событию, поскольку обработчик обычно является методом экземпляра (требующего экземпляра), экземпляр класса, содержащего прослушиватель, остается в памяти до тех пор, пока сохраняется эта ссылка. Если экземпляр слушателя по очереди содержит ссылки на другие классы (например, обратные ссылки на содержащие объекты), куча может оставаться довольно большой еще долго после того, как слушатель вышел из всех других областей.

Может быть, кто-то с немного более эзотерическими знаниями о Delegate и MulticastDelegate сможет пролить свет на это. На мой взгляд, настоящая утечка МОЖЕТ быть возможной, если все следующее верно:

  • Приемнику событий требуется, чтобы внешние / неуправляемые ресурсы были освобождены путем реализации IDisposable, но это не так, или
  • Делегат многоадресной передачи события НЕ вызывает методы Dispose () из переопределенного метода Finalize (), и
  • Класс, содержащий событие, не вызывает Dispose () для каждой цели делегата через собственную реализацию IDisposable или в Finalize ().

Я никогда не слышал о лучших практиках, связанных с вызовом Dispose () для целей делегатов, а тем более слушателей событий, поэтому я могу только предположить, что разработчики .NET знали, что они делали в этом случае. Если это так, и MulticastDelegate, стоящий за событием, пытается правильно избавиться от слушателей, то все, что необходимо, - это правильная реализация IDisposable для класса прослушивания, который требует удаления.

...