Es scheint, dass System.Timers.Timer
Instanzen durch einen Mechanismus am Leben erhalten werden, System.Threading.Timer
Instanzen jedoch nicht.
Beispielprogramm mit periodischem System.Threading.Timer
und automatischem Zurücksetzen System.Timers.Timer
:
class Program
{
static void Main(string[] args)
{
var timer1 = new System.Threading.Timer(
_ => Console.WriteLine("Stayin alive (1)..."),
null,
0,
400);
var timer2 = new System.Timers.Timer
{
Interval = 400,
AutoReset = true
};
timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
timer2.Enabled = true;
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Invoking GC.Collect...");
GC.Collect();
Console.ReadKey();
}
}
Wenn ich dieses Programm ausführe (.NET 4.0 Client, Release, außerhalb des Debuggers), wird nur das System.Threading.Timer
GC'ed:
Stayin alive (1)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Invoking GC.Collect...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
EDIT : Ich habe Johns Antwort unten akzeptiert, aber ich wollte es ein wenig erläutern.
Wenn Sie das obige Beispielprogramm ausführen (mit einem Haltepunkt bei Sleep
), sehen Sie hier den Status der betreffenden Objekte und die GCHandle
Tabelle:
!dso
OS Thread Id: 0x838 (2104)
ESP/REG Object Name
0012F03C 00c2bee4 System.Object[] (System.String[])
0012F040 00c2bfb0 System.Timers.Timer
0012F17C 00c2bee4 System.Object[] (System.String[])
0012F184 00c2c034 System.Threading.Timer
0012F3A8 00c2bf30 System.Threading.TimerCallback
0012F3AC 00c2c008 System.Timers.ElapsedEventHandler
0012F3BC 00c2bfb0 System.Timers.Timer
0012F3C0 00c2bfb0 System.Timers.Timer
0012F3C4 00c2bfb0 System.Timers.Timer
0012F3C8 00c2bf50 System.Threading.Timer
0012F3CC 00c2bfb0 System.Timers.Timer
0012F3D0 00c2bfb0 System.Timers.Timer
0012F3D4 00c2bf50 System.Threading.Timer
0012F3D8 00c2bee4 System.Object[] (System.String[])
0012F4C4 00c2bee4 System.Object[] (System.String[])
0012F66C 00c2bee4 System.Object[] (System.String[])
0012F6A0 00c2bee4 System.Object[] (System.String[])
!gcroot -nostacks 00c2bf50
!gcroot -nostacks 00c2c034
DOMAIN(0015DC38):HANDLE(Strong):9911c0:Root: 00c2c05c(System.Threading._TimerCallback)->
00c2bfe8(System.Threading.TimerCallback)->
00c2bfb0(System.Timers.Timer)->
00c2c034(System.Threading.Timer)
!gchandles
GC Handle Statistics:
Strong Handles: 22
Pinned Handles: 5
Async Pinned Handles: 0
Ref Count Handles: 0
Weak Long Handles: 0
Weak Short Handles: 0
Other Handles: 0
Statistics:
MT Count TotalSize Class Name
7aa132b4 1 12 System.Diagnostics.TraceListenerCollection
79b9f720 1 12 System.Object
79ba1c50 1 28 System.SharedStatics
79ba37a8 1 36 System.Security.PermissionSet
79baa940 2 40 System.Threading._TimerCallback
79b9ff20 1 84 System.ExecutionEngineException
79b9fed4 1 84 System.StackOverflowException
79b9fe88 1 84 System.OutOfMemoryException
79b9fd44 1 84 System.Exception
7aa131b0 2 96 System.Diagnostics.DefaultTraceListener
79ba1000 1 112 System.AppDomain
79ba0104 3 144 System.Threading.Thread
79b9ff6c 2 168 System.Threading.ThreadAbortException
79b56d60 9 17128 System.Object[]
Total 27 objects
Wie John in seiner Antwort betonte, registrieren beide Timer ihren Rückruf ( System.Threading._TimerCallback
) in der GCHandle
Tabelle. Wie Hans in seinem Kommentar betonte, state
bleibt der Parameter auch dann am Leben.
Wie John betonte, System.Timers.Timer
bleibt der Grund am Leben, weil er vom Rückruf referenziert wird (er wird als state
Parameter an das Innere übergeben System.Threading.Timer
); Ebenso ist der Grund, warum wir System.Threading.Timer
GC'ed sind, der, dass es nicht durch seinen Rückruf referenziert wird.
Das Hinzufügen eines expliziten Verweises auf timer1
den Rückruf (z. B. Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")")
) reicht aus, um GC zu verhindern.
Die Verwendung des Einzelparameter-Konstruktors System.Threading.Timer
funktioniert auch, da sich der Timer dann selbst als state
Parameter referenziert . Der folgende Code hält beide Timer nach dem GC am Leben, da sie jeweils durch ihren Rückruf aus der GCHandle
Tabelle referenziert werden :
class Program
{
static void Main(string[] args)
{
System.Threading.Timer timer1 = null;
timer1 = new System.Threading.Timer(_ => Console.WriteLine("Stayin alive (1)..."));
timer1.Change(0, 400);
var timer2 = new System.Timers.Timer
{
Interval = 400,
AutoReset = true
};
timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
timer2.Enabled = true;
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Invoking GC.Collect...");
GC.Collect();
Console.ReadKey();
}
}
quelle
timer1
überhaupt Müll gesammelt? Ist es nicht noch im Geltungsbereich?Antworten:
Sie können diese und ähnliche Fragen mit windbg, sos und beantworten
!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>
In beiden Fällen muss der native Timer die GC des Rückrufobjekts verhindern (über ein GCHandle). Der Unterschied besteht darin, dass im Fall
System.Timers.Timer
der Rückrufreferenzen dasSystem.Timers.Timer
Objekt (das intern mit a implementiert wirdSystem.Threading.Timer
)quelle
Ich habe dieses Problem kürzlich gegoogelt, nachdem ich mir einige Beispielimplementierungen von Task.Delay angesehen und einige Experimente durchgeführt habe.
Es stellt sich heraus, ob System.Threading.Timer GCd ist oder nicht, hängt davon ab, wie Sie es konstruieren !!!
Wenn es nur mit einem Rückruf erstellt wird, ist das Statusobjekt der Timer selbst, und dies verhindert, dass es GC-fähig wird. Dies scheint nirgendwo dokumentiert zu sein und doch ist es ohne es äußerst schwierig, Feuer zu erzeugen und Timer zu vergessen.
Ich fand dies aus dem Code unter http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/Threading/Timer@cs / 1 / Timer @ cs
Die Kommentare in diesem Code geben auch an, warum es immer besser ist, den Nur-Rückruf-Ctor zu verwenden, wenn der Rückruf auf das von new zurückgegebene Timer-Objekt verweist, da sonst ein Race-Fehler auftreten könnte.
quelle
In timer1 geben Sie einen Rückruf. In timer2 to schließen Sie einen Event-Handler an. Dadurch wird ein Verweis auf Ihre Programmklasse eingerichtet, was bedeutet, dass der Timer nicht GCed wird. Da Sie den Wert von timer1 nie wieder verwenden (im Grunde das gleiche, als ob Sie var timer1 = entfernt hätten), ist der Compiler intelligent genug, um die Variable zu optimieren. Wenn Sie den GC-Aufruf drücken, verweist nichts mehr auf timer1, so dass es gesammelt wird.
Fügen Sie nach Ihrem GC-Aufruf eine Console.Writeline hinzu, um eine der Eigenschaften von timer1 auszugeben, und Sie werden feststellen, dass sie nicht mehr erfasst wird.
quelle
Program
Klasse, und selbst wenn dies der Fall wäre, würde dies nicht verhindern, dass der Timer GC-fähig wird.Zu Ihrer Information, ab .NET 4.6 (wenn nicht früher) scheint dies nicht mehr zuzutreffen. Wenn Ihr Testprogramm heute ausgeführt wird, führt dies nicht dazu, dass bei einem der beiden Timer Müll gesammelt wird.
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)...
Wenn ich mir die Implementierung von System.Threading.Timer anschaue , scheint dies sinnvoll zu sein, da es den Anschein hat, dass die aktuelle Version von .NET eine verknüpfte Liste aktiver Zeitgeberobjekte verwendet und diese verknüpfte Liste von einer Mitgliedsvariablen in TimerQueue (d. H. ein Singleton-Objekt, das von einer statischen Elementvariablen auch in TimerQueue am Leben erhalten wird). Infolgedessen bleiben alle Timer-Instanzen am Leben, solange sie aktiv sind.
quelle
System.Threading.Timer
Instanz in .NET 4.6 gesammelt wird. Stellen Sie sicher, dass Sie den Code im Release-Modus mit aktivierten Optimierungen kompilieren. Die von Ihnen erwähnte verknüpfte Liste enthält HilfsobjekteTimerQueueTimer
. Es verhindert nicht, dass die ursprünglicheSystem.Threading.Timer
Instanz vom GC erfasst wird. (JedeSystem.Threading.Timer
Instanz verweist auf ihr eigenesTimerQueueTimer
Objekt, aber nicht umgekehrt. Wenn dasSystem.Threading.Timer
vom GC erfasst wird, wird seinTimerQueueTimer
Objekt vom~TimerHolder
Finalizer aus der Warteschlange entfernt .)Main
Methode verschieben.