Могут ли таймеры автоматически получать мусор?
Когда вы используете Timer
или Thread
, которые будут запускаться только на протяжении всего срока службы программы, вам нужно сохранить ссылку на них, чтобы предотвратить их сбор мусора?
Пожалуйста, оставьте в стороне тот факт, что нижеприведенная программа может иметь Timer
как статическую переменную в классе, это просто пример игрушек, чтобы показать проблему.
public class Program
{
static void Main(string[] args)
{
CreateTimer();
Console.ReadLine();
}
private static void CreateTimer()
{
var program = new Program();
var timer = new Timer();
timer.Elapsed += program.TimerElapsed;
timer.Interval = 30000;
timer.AutoReset = false;
timer.Enabled = true;
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
var timerCast = (Timer)sender;
Console.WriteLine("Timer fired at in thread {0}", GetCurrentThreadId());
timerCast.Enabled = true;
}
~Program()
{
Console.WriteLine("Program Finalized");
}
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
}
Может ли таймер получить собранный в этом примере выше? Я запустил его некоторое время, и у меня не было исключения, и не было сообщено сообщение ~Program()
.
ОБНОВЛЕНИЕ: Я узнал из этот вопрос (спасибо sethcran) что потоки отслеживаются CLR, но мне все же хотелось бы получить ответ о таймерах.
Ответы
Ответ 1
Это только проблема с классом System.Threading.Timer, если вы в противном случае не храните ссылку на него где-то. Он имеет несколько перегрузок конструктора, важные из которых занимают объект состояния. CLR обращает внимание на этот объект состояния. Пока он упоминается где-то, CLR сохраняет таймер в очереди таймера, и объект таймера не будет собирать мусор. Большинство программистов не будут использовать этот объект состояния, статья MSDN, безусловно, не объясняет его роли.
System.Timers.Timer - это оболочка для класса System.Threading.Timer, что упрощает его использование. В частности, он будет использовать этот объект состояния и сохранить ссылку на него, пока включен таймер.
Обратите внимание, что в вашем случае свойство таймера Enabled является ложным, когда оно входит в обработчик события Elapsed, поскольку у вас есть AutoReset = false. Таким образом, таймер имеет право на сбор, как только он входит в ваш обработчик событий. Но вы не попадаете в неприятности, ссылаясь на аргумент отправителя, требуя установить Enabled обратно в true. Из-за чего джиттер сообщает ссылку, поэтому у вас нет проблемы.
Будьте осторожны с обработчиком событий Elapsed. Любое исключение, заброшенное внутри этого метода, будет проглочено без диагностики. Это также означает, что вы не установите Enabled обратно в true. Вы должны использовать try/catch, чтобы сделать что-то разумное. Если вы не намерены заканчивать свою программу, как минимум, вам нужно будет сообщить своей основной программе, что что-то больше не работает. Включение Enabled = true в предложении finally может избежать сбора мусора таймера, но с риском того, что ваша программа будет перебрасывать исключения снова и снова.
Ответ 2
Добавьте этот код в программу и запустите его. Вы увидите, что таймер НЕ собран.
private void DoStuff()
{
CreateTimer();
Console.WriteLine("Timer started");
int count = 0;
for (int x = 0; x < 1000000; ++x)
{
string s = new string("just trying to exercise the garbage collector".Reverse().ToArray());
count += s.Length;
}
Console.WriteLine(count);
Console.Write("Press Enter when done:");
Console.ReadLine();
}
private void Ticktock(object s, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine("Ticktock");
}
private void CreateTimer()
{
System.Timers.Timer t = new System.Timers.Timer(); // Timer(Ticktock, null, 1000, 1000);
t.Elapsed += Ticktock;
t.Interval = 1000;
t.AutoReset = true;
t.Enabled = true;
}
Таким образом, ответ на ваш вопрос заключается в том, что таймер не имеет права на сбор и не собирается, если вы не поддерживаете ссылку на него.
Интересно отметить, что если вы запустите тот же тест с помощью System.Threading.Timer
, вы обнаружите, что таймер собран.
Ответ 3
Давайте проведем эксперимент:
private static void UnderTest() {
// Timer is a local varibale; its callback is local as well
System.Threading.Timer timer = new System.Threading.Timer(
(s) => { MessageBox.Show("Timer!"); },
null,
1000,
1000);
}
...
// Let perform Garbage Colelction manually:
// we don't want any surprises
// (e.g. system starting collection in the middle of UnderTest() execution)
GC.Collect(2, GCCollectionMode.Forced);
UnderTest();
// To delay garbage collection
// Thread.Sleep(1500);
// To perform Garbage Collection
// GC.Collect(2, GCCollectionMode.Forced);
До сих пор
- если мы будем комментировать оба
Thread.Sleep(1500);
и GC.Collect(2, GCCollectionMode.Forced);
мы увидим появившиеся сообщения: таймер работает - если мы раскомментируем
GC.Collect(2, GCCollectionMode.Forced);
ничего не увидим: таймер запускается чем он собран - если мы раскомментируем оба
Thread.Sleep(1500);
и GC.Collect(2, GCCollectionMode.Forced);
мы увидим одно окно сообщения: таймер запускается, выключается одно окно сообщения, а затем таймер собирается
Так что Timer
собраны как любые другие экземпляры объектов.