Про сборку мусора
От: Аноним  
Дата: 19.01.11 15:51
Оценка:
почему результат работы такого кода в релизе и дебаге отличается?


 static void Main(string[] args)
        {
            Timer t = new Timer(DoWork, null, 0, 2000);

            Console.ReadKey();
        }

        private static void DoWork(object obj)
        {
            Console.WriteLine("I am timer");

            GC.Collect();
        }
Re: Про сборку мусора
От: MozgC США http://nightcoder.livejournal.com
Дата: 19.01.11 16:18
Оценка: 85 (8) +2
Здравствуйте, Аноним, Вы писали:

А>почему результат работы такого кода в релизе и дебаге отличается?


В release-режиме включаются различные оптимизации, одна из которых позволяет собирать (garbage collect) объекты, которые больше не будут использоваться в данном методе. Таким образом, память может быть освобождена быстрее.
Поэтому в релизе, с включенными оптимизациями, GC видит, что переменная t (которая является единственной ссылкой на объект Timer в куче) не используется в методе Main(), а значит GC при сборке мусора может сразу задиспозить объект таймера, что приводит к тому, что коллбек перестает выполняться.
В debug'е такой оптимизации не происходит и GC не будет диспозить таймер пока метод Main() не завершится. А метод Main() не завершается пока пользователь не нажмет на любую клавишу. Поэтому до того, как пользователь не нажмет на клавишу, таймер остается рабочим и периодически выводит на экран сообщение.

Чуть более подробно об этом можно прочитать, например, в книге Албахари "C# 4.0 in a Nutshell":

The timer in the System.Threading namespace, however, is special. The .NET Framework doesn’t hold references to active threading timers; it instead references the callback delegates directly. This means that if you forget to dispose of a threading timer, a finalizer can (and will) fire—and this will automatically stop and dispose the timer. This can create a different problem, however, which we can illustrate as follows:

static void Main()
{
  var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000);
  GC.Collect();
  System.Threading.Thread.Sleep (10000); // Wait 10 seconds
}

static void TimerTick (object notUsed) { Console.WriteLine ("tick"); }

If this example is compiled in “release” mode (debugging disabled and optimizations enabled), the timer will be collected and finalized before it has a chance to fire even once! Again, we can fix this by disposing of the timer when we’re done with it:

using (var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000))
{
  GC.Collect();
  System.Threading.Thread.Sleep (10000); // Wait 10 seconds
}

The implicit call to tmr.Dispose at the end of the using block ensures that the tmr variable is “used” and so not considered dead by the GC until the end of the block. Ironically, this call to Dispose actually keeps the object alive longer!


Так же, для общего развития, можно прочитать и про таймер из пространства имен System.Timers, он ведет себя по-другому:

Forgotten timers can also cause memory leaks (we discuss timers in Chapter 21). There are two distinct scenarios, depending on the kind of timer. Let’s first look at the timer in the System.Timers namespace. In the following example, the Foo class (when instantiated) calls the tmr_Elapsed method once every second:

using System.Timers;
class Foo
{
  Timer _timer;

  Foo()
  {
    _timer = new System.Timers.Timer { Interval = 1000 };
    _timer.Elapsed += tmr_Elapsed;
    _timer.Start();
  }
  
  void tmr_Elapsed (object sender, ElapsedEventArgs e) { ... }
}


Unfortunately, instances of Foo can never be garbage-collected! The problem is the .NET Framework itself holds references to active timers so that it can fire their Elapsed events. Hence:

• The .NET Framework will keep _timer alive.
• _timer will keep the Foo instance alive, via the tmr_Elapsed event handler.

The solution is obvious when you realize that Timer implements IDisposable. Disposing of the timer stops it and ensures that the .NET Framework no longer references the object:

class Foo : IDisposable
{
  ...
  public void Dispose() { _timer.Dispose(); }
}

A good guideline is to implement IDisposable yourself if any field in your class is assigned an object that implements IDisposable.

The WPF and Windows Forms timers behave in exactly the same way, with respect to what’s just been discussed.

Re[2]: Про сборку мусора
От: MozgC США http://nightcoder.livejournal.com
Дата: 19.01.11 18:54
Оценка:
Сейчас еще раз прочитал свой пост, и заметил что как-то нехорошо я написал, что "GC задиспозит объект таймера". Конечно же, GC сам напрямую не вызывает метод IDisposable.Dispose() (только если косвенно, когда у объекта переопределен Finalizer и из Finalizer'а вызывается Dispose()).

Кстати, вот еще абзац по теме, из той же книги:

In debug mode with optimizations disabled, the lifetime of an object referenced by a local variable extends to the end of the code block to ease debugging. Otherwise, it becomes eligible for collection at the earliest point at which it’s no longer used.

Re[3]: Про сборку мусора
От: MozgC США http://nightcoder.livejournal.com
Дата: 19.01.11 19:31
Оценка: 12 (1)
Тут возник вопрос: почему Timer "выключается" после garbage collection, ведь у него не переопределен Finalizer, т.е. GC по идее при сборке мусора с ним ничего не делает?
Оказалось все очень просто: внутри System.Threading.Timer используется объект типа TimerBase, у которого Finalizer уже переопределен. Его Finalizer вызывается при сборке мусора, и выполняется код, останавливающий таймер.
Таймер же из пространства имен System.Timers внутри себя просто использует System.Threading.Timer, так же дальше схема та же самая.
Может кому пригодится, а то я щас сам в гугле искал сначала, но ничего не нашел, потом в исходниках заметил TimerBase
Re[4]: Еще есть и winforms timer
От: BluntBlind  
Дата: 20.01.11 03:16
Оценка:
Здравствуйте, MozgC, Вы писали:

Тоже разбирался как-то
Автор: BluntBlind
Дата: 17.04.07
давно ...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.