Вы можете ответить на этот и подобные вопросы с помощью windbg, sos и !gcroot
0:008> !gcroot -nostacks 0000000002354160
DOMAIN(00000000002FE6A0):HANDLE(Strong):241320:Root:00000000023541a8(System.Thre
ading._TimerCallback)->
00000000023540c8(System.Threading.TimerCallback)->
0000000002354050(System.Timers.Timer)->
0000000002354160(System.Threading.Timer)
0:008>
. В обоих случаях встроенный таймер должен предотвратить GC объекта обратного вызова (через GCHandle). Разница в том, что в случае System.Timers.Timer
обратный вызов ссылается на объект System.Timers.Timer
(который реализован внутренне с помощью System.Threading.Timer
)
В таймере 1 вы даете ему обратный вызов. В timer2 вы подключаете обработчик событий; это настраивает ссылку на ваш класс программы, что означает, что таймер не будет GCed. Поскольку вы никогда больше не используете значение timer1 (в основном так же, как если бы вы удалили var timer1 =), компилятор достаточно умен, чтобы оптимизировать эту переменную. Когда вы нажмете на вызов GC, ничего больше не ссылается на timer1, поэтому его «собрано».
Добавить консоль. Записи после вызова GC для вывода одного из свойств таймера 1, и вы заметите, что он больше не собирается .
FYI, с .NET 4.6 (если не раньше), это больше не будет истинным.
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Invoking GC.Collect...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Когда я смотрю на реализацию System.Threading.Timer , это означает, что ваша тестовая программа, когда она выполняется сегодня, не приводит к сбою мусора. что в текущей версии .NET используется связанный список активных объектов таймера, и этот связанный список удерживается переменной-членом внутри TimerQueue (которая представляет собой одноэлементный объект, поддерживаемый статическим переменная члена также в TimerQueue). В результате все экземпляры таймера будут оставаться в живых до тех пор, пока они активны.
Я недавно искал эту проблему, посмотрев некоторые примеры реализации Task.Delay и сделав некоторые эксперименты.
Оказывается, независимо от того, является ли System.Threading.Timer GCd, зависит от того, как вы постройте его !!!
Если построено только с обратным вызовом, то объект состояния будет сам таймер, и это предотвратит его GC'd. Это, кажется, нигде не документировано, но без него чрезвычайно сложно создать огонь и забыть таймеры.
Я нашел это из кода в http://www.dotnetframework.org/ default.aspx / DotNET / DotNET / 8 @ 0 / untmp / whidbey / REDBITS / ndp / clr / src / BCL / System / Threading / Timer @ cs / 1 / Timer @ cs
Комментарии в этом коде также указывают, почему всегда лучше использовать callback-only ctor, если обратный вызов ссылается на объект таймера, возвращаемый новым, поскольку в противном случае может быть ошибка гонки.