Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > .NET для новичков > Вопрос по Garbage Collection


Автор: Renaz 2.2.2014, 14:55
Доброго времени суток!

Довольно длительное время пишу на плюсах, привык к тому, что все время приходиться следить явно или не очень за корректным освобождением ресурсов. Посему имеется кучка непоняток с c#'овым сборщиком мусора. Например мне не понятно, корректен ли нижеследующий кусок кода, не помечает ли GC экземпляр MyTimer для удаления сразу после выхода из метода ResendCurrentPacketWithDelay? И если не помечает, то когда он это сделает? Нужно ли в TimerOnElapsed добавить  timer.Elapsed -= TimerOnElapsed; для корректной отработки сборщика мусора? По логике программы экземпляр MyTimer можно(нужно) удалить после того, как он вызовет свой колбек Elapsed. Спасибо.

Код

private void ResendCurrentPacketWithDelay(double delay) {
            var timer = new MyTimer();
            timer.Elapsed += TimerOnElapsed;
            timer.Start(delay);
        }

private void TimerOnElapsed(object sender, TimerEventArg timerEventArg) {
            var timer = (ITimer) sender;
            if (timer != null) {
                  timer.stop();
                  ResendCurrentPacket();
            }
        }

Автор: jonie 2.2.2014, 14:57
без знаний как реализован таймер мы не сможем дать вам ответ, ибо например в Start методе таймер может добавлять задачу, с ссылкой на себя и эта задача пока работает будет держать на него ссылку... и т.д.

Автор: Renaz 2.2.2014, 15:08
Цитата(jonie @ 2.2.2014,  14:57)
без знаний как реализован таймер мы не сможем дать вам ответ, ибо например в Start методе таймер может добавлять задачу, с ссылкой на себя и эта задача пока работает будет держать на него ссылку... и т.д.

Собственно MyTimer - просто обёртка над System.Timers.Timer:

Код

    public class MyTimer : ITimer
    {
        private readonly System.Timers.Timer _timer = new Timer();

        public Timer() {
            _timer.Elapsed += _timer_Elapsed;    
        }

        void _timer_Elapsed(object sender, ElapsedEventArgs e) {
            Elapsed(this, new TimerEventArg());
        }   
        public void Dispose() {
            _timer.Dispose();
        }

        public void Start(double interval) {
            _timer.Interval = interval;
            _timer.Start();
        }

        public void Stop() {
            _timer.Stop();
        }

        public event TimerEventHandler Elapsed;

        public object Clone() {
            return this.MemberwiseClone();
        }
    }


Автор: jonie 2.2.2014, 19:33
начнем с того что у вас плохо реализован IDisposable - смотрите в MSDN как правильно.

И он помечан как ExternalThreading, а это значит что его объект надо сохранять (ссылку на него) самостоятельно - см. пример http://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx

Автор: Renaz 2.2.2014, 20:51
Цитата(jonie @ 2.2.2014,  19:33)
начнем с того что у вас плохо реализован IDisposable - смотрите в MSDN как правильно.

И он помечан как ExternalThreading, а это значит что его объект надо сохранять (ссылку на него) самостоятельно - см. пример http://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx

Спасибо. Читал МСДН, мало понял. Трюк с предотвращением повторного вызова кода понятен, не понятна разница между явным вызовом Dispose() программистом, и автоматическим вызовом деструктора(?) сборщиком? По идее одна задача должна быть у них - освободить какие-либо занятые ресурсы? И еще вопрос по GC.KeepAlive - в msdn написано, что 
Цитата

The KeepAlive method performs no operation and produces no side effects other than extending the lifetime of the object passed in as a parameter.
 На сколько этот метод увеличивает livetime объекта? Ниже исправленный код (возможно до сих пор не корректный), написанный по образу и подобию мсдн'овского, но всё равно логика не понятна.

Код

        private void ResendCurrentPacketWithDelay(double delay) {
            var timer = new MyTimer;
            GC.KeepAlive(timer);
            timer.Elapsed += TimerOnElapsed;
            timer.Start(delay);
        }

        private void TimerOnElapsed(object sender, TimerEventArg timerEventArg) {
            var timer = (ITimer) sender;
            if (timer == null) return;
            ResendCurrentPacket();
            timer.Dispose();
        }


и часть кода из MyTimer:
Код

        public MyTimer() {
            _disposed = false;
            _timer.Elapsed += _timer_Elapsed;
        }

        ~MyTimer() {
            Dispose(false);
        }

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

        private void Dispose(bool disposing) {
            if (_disposed) return;
            if (disposing) {
                _timer.Elapsed -= _timer_Elapsed;
                _timer.Dispose();    
            }
            _timer = null;    
            _disposed = true;
        }

Автор: jonie 3.2.2014, 08:46
Цитата(Renaz @  2.2.2014,  21:51 Найти цитируемый пост)
возможно до сих пор не корректный

не возможно, а так и есть.. GC.KeepAlive делаете, но не отзываете своё требование..
вообще сохраните экземпляр таймера и не парьте мозг, GC.KeepAlive не для этого создан.

Автор: Renaz 3.2.2014, 09:11
Цитата(jonie @ 3.2.2014,  08:46)
Цитата(Renaz @  2.2.2014,  21:51 Найти цитируемый пост)
возможно до сих пор не корректный

не возможно, а так и есть.. GC.KeepAlive делаете, но не отзываете своё требование..
вообще сохраните экземпляр таймера и не парьте мозг, GC.KeepAlive не для этого создан.

Ок, понял. Не посоветуете какую-нибудь литературу по GC в .NET почитать? А резюмируя по первому вопросу - объект помечается для удаления если на него самого у никого нет ссылок и он сам не имеет ссылок на активные объекты?

Автор: jonie 3.2.2014, 10:16
Алгоритм действия GC примерно такой (сииильно упрощенно): все объекты помечаются как удаленные, затем GC проходит по графу и оживляет те, на которые есть ссылки из графа, после этого "удаляет" оставшееся.
)
Про GC читать книжку Under the Hood of .NET Memory Management (Chris Farrell and Nick Harrison), ну и рихтера, конечно перед этим всем...

Автор: Certain 3.2.2014, 10:57
Хотелось бы добавить к вышесказанному:
1. Советую почитать отличную статью (помимо главы Джефри Рихтера про GC) про эталон освобождения ресурсов http://habrahabr.ru/post/129283/, особенно про способы реализации данного паттерна для объектов, содержащих управляемые и/или неуправляемые ресурсы.
В Вашем случае объект является обёрткой над управляемым ресурсом Timer, такой реализации интерфейса IDisposable будет достаточно (реализация финализатора в данном случае будет лишней, нет необходимости)
Код

 public void Dispose() {
            _timer.Dispose();
        }

2. 
Код

private void TimerOnElapsed(object sender, TimerEventArg timerEventArg) {
            var timer = (ITimer) sender;
            if (timer == null) return;
            ResendCurrentPacket();
            timer.Dispose();
        }


timer.Dispose(); лучше вызвать до метода ResendCurrentPacket();.
Если требуется много времени для работы метода ResendCurrentPacket();, таймер может сработать ещё раз до завершения ResendCurrentPacket();.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)