2012-09-24 1 views
4

Я читал об управлении памятью и сталкивался с ситуацией в проекте, где книга или Google придумали точный ответ. Я уже знаю, что делегаты управляют объектами, а события - экземплярами делегата. Сказав это, экземпляры делегатов будут удалены из памяти после завершения приложения.Что делать с ссылками делегата/события в классе, который реализует IDisposable

Я не могу понять, как обеспечить, чтобы внешний код освободил все ссылки на события до того момента, как мой класс будет удален (явно или GC). В качестве примера класс A раскрывает событие, а класс B его использует. Класс B вызывает Dispose в классе A, не высвобождая ссылки на делегатов. Конечно, мы не можем выбросить ошибку из самого метода Dispose.

Ниже приведен класс с делегатом и другой, который его потребляет.

public class ClassB 
{ 
    private ClassA A { get; set; } 

    public ClassB() 
    { 
     this.A = new ClassA(); 
     this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed); 
    } 

    public void Process() 
    { 
     this.A.Process(); 
    } 

    public void ClassA_Processed (ClassA sender, EventArgs e) 
    { 
     // Do something. 

     // Code written by another developer does not free up events before calling Dispose. 

     this.A.Dispose(); 
     this.A = null; 
    } 
} 

public class ClassA: IDisposable 
{ 
    public delegate void DelegateProcessed (A sender, EventArgs e); 
    public event DelegateProcessed OnProcessed = null; 

    ~ClassA() { this.Dispose(false); } 

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

    private void Dispose (bool disposing) 
    { 
     if (!this.Disposed) 
     { 
      if (disposing) 
      { 
       // Dispose managed resources here. 
       // Is it possible/advisable to dispose of delegates/events here? 
       // Will this adversely affect the consumer class? 
       this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed); 
      } 
     } 
     this.Disposed = true; 
    } 

    public void Process() { this.OnProcessed(this, new EventArgs()); } 

    public void ClassA_Processed (ClassA sender, EventArgs e) { } 
} 

Цель состоит в том, чтобы гарантировать, что ClassA подходит для сбора мусора независимо от того, что делает разработчик с ClassB. Суть заключается в том, чтобы свести к минимуму количество времени, которое ClassA проводит в памяти, даже если потребитель небрежен.

ОБНОВЛЕНИЕ: Из ответов видно, что события не должны быть явно удалены из ClassA. Что касается основного вопроса, то, по-видимому, слабыми рекомендациями являются способы, как показано ниже. Цель состоит в том, чтобы минимизировать время, в течение которого ClassA остается в памяти. Пожалуйста, дайте мне знать в случае, если я упустил любой из них.

+0

Здесь есть что-то о вашей логике. Это класс A, который будет содержать делегатов от cl ass B в списке вызовов 'OnProcessed', а не наоборот. – spender

+0

@spender: я написал код в редакторе SO, поэтому может быть ошибка, но я не следую вашей точке. ClassA - это тот, у кого есть делегат, а classB содержит ссылку. Я пытаюсь удалить referecnce из ClassA, так что, даже если ClassB забывает, ClassA может претендовать на сбор мусора. Если вы видите ошибку, сообщите мне об этом, и я сделаю это. –

+0

Вы можете смело позволить экземплярам класса A выходить за рамки без каких-либо изменений, которые мы видели в вашем коде, хранящемся в нем через экземпляры делегата. Однако в вашем коде, если вы хотите, чтобы экземпляр B выпал из области видимости, он не будет собран, потому что есть делегат, указывающий на метод 'ClassA_Processed', который хранится в списке вызовов' OnProcessed' в вашем экземпляре класс A – spender

ответ

1

Вместо «классических» подписки на события вы должны взглянуть на Weak Event Patterns.

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

Это вызывает серьезные утечки памяти.

Если вы используете шаблон «Слабые события», вы позволяете GabageCollector лучше определять, ссылается ли объект на объект, или если события являются единственными ссылками. В этом случае объекты собираются и ваши ресурсы освобождаются.

+0

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

+0

@ RaheelKhan: На самом деле это не так уж и важно. Но важно, что мы обнаружили, что мы обнаружили трудный путь на работе, когда многие наши клиентские машины неожиданно разбились с неожиданными «OutOfMemoryExceptions» ... :-) –

+0

Это потому, что слабые ссылки поощряют разработчиков к ленивым? Или вы имели в виду, что исключения из-за памяти были результатом отказа от жестких ссылок? –

2

IDisposable Используется для детерминированного освобождения неуправляемых ресурсов.

Нет необходимости удалять обработчики событий. Например, если вы изучите классы Windows Forms Form и UserControl или классы ASP.NET Page и UserControl, все из которых являются IDisposable, вы увидите широкое использование событий и отсутствие специальной обработки во время удаления.

+0

Что касается WinForms, в частности, я часто сталкивался с проблемами, когда какой-либо поток или таймер пытается обновить интерфейс после того, как форма была удалена. Поэтому я не уверен, как интерпретировать ваш пост как решение. –

+0

@ RaheelKhan, вам нужно создать свое приложение, чтобы избежать этого: в приведенном примере вы должны проверить 'Form.IsDisposed' перед вызовом любых методов в форме, когда ваш код вызывается из другого потока. Но это не имеет никакого отношения к обработчикам событий. – Joe

+0

@Joe: Извините, я не согласен. Неисправленная подписка на события может сохранить объект живым навсегда и может вызвать затруднительные утечки памяти. Использование шаблона слабых событий помогает в решении этой проблемы (см. Мой собственный ответ). –

1

Этот раздел кода:

private ClassA A { get; set; } 

public ClassB() 
{ 
    this.A = new ClassA(); 
    this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed); 
} 

означает, что Вам не нужно делать ничего .

A B экземпляр имеет экземпляр A, а у A есть ссылка (через мероприятие) на B.

Когда B становится недоступным, тогда будет также собрано A (GC и круговые ссылки).

Когда «A» выставлен (длиннее) перед B, тогда также будет собрано «A» (направленность).

Интерфейс IDispoable на A бессмыслен.


А по поводу реализации:

// class B 
    this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed); 

// in classA 
    this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed); 

Это не будет работать, 2 разные this означает, что они являются 2 разными методами.

+0

Я понимаю это. Сказав это, он не гарантирует, что A будет собрано задолго до B, если область охвата B является прикладной. Я что-то упускаю? –

+0

На самом деле IDisposable на A по другим причинам. Но я также хотел убедиться, что жизнь A в памяти сведена к минимуму. –

+0

OK, но тогда ваш код не делает этого. Только когда A удерживает (un) управляемые ресурсы или занимает значительную часть памяти, 'Dispose()' имеет какой-либо эффект. В текущем коде 'this.A = null;' достаточно. –

0

Правильно написанный класс должен в своем методе IDisposable.Dispose отказаться от подписки на любые события, на которые он подписал. Если объект, чье событие подписано, имеет время жизни GC, сопоставимое с полезным временем жизни объекта, который подписался (что является очень распространенным случаем), не имеет значения, очищена ли подписка или нет. К сожалению, если A заброшен без отмены подписки на событие B, и что-то держит долговечную ссылку на B (намеренно или нет), все, что держит B, будет также оставаться в живых A и что угодно, что A имеет прямую или косвенная ссылка (включая объекты, которые имеют активные подписки на события от A). Очень легко попасть в большие леса взаимосвязанных объектов, которые обычно будут иметь право на сбор мусора, но которые будут все должны быть в живых, пока не нужен.

Очень плохой подписки на мероприятия и отмены подписки настолько неловкие. Если бы существовал тип объекта, связанный с событиями, объект, который собирался подписаться на различные события, мог использовать объект «диспетчер событий» для управления подписками (чтобы можно было что-то сказать MyEventManager.Subscribe(SomeObject.SomeEvent, someProc), а затем MyEventManager.Dispose отказаться от подписки на все события, на которые он установил подписки. К сожалению, нет приемлемого способа, чтобы метод принимал событие в качестве параметра и, следовательно, не имел способа иметь общий целевой класс для управления входящими подписками. Лучше всего было бы, вероятно, иметь класс CleanupManager который возьмет пару делегатов и будет вызываться нечто вроде `MyCleaner.Register (() => {SomeObject.SomeEvent + = someProc;},() => {SomeObject.SomeEvent - = someProc();}), но это кажется довольно неудобно.

Смежные вопросы