Why does the timer support my object?

Foreword I know how to solve a problem. I want to know why . Please read the question from top to bottom.

As we all (should) know, adding event handlers can cause a memory leak in C #. See Why and How to Avoid Event Handler Memory Leaks?

On the other hand, objects often have similar or related life cycles, and deregistration from event loggers is optional. Consider this example:

using System; public class A { private readonly B b; public A(B b) { this.b = b; b.BEvent += b_BEvent; } private void b_BEvent(object sender, EventArgs e) { // NoOp } public event EventHandler AEvent; } public class B { private readonly A a; public B() { a = new A(this); a.AEvent += a_AEvent; } private void a_AEvent(object sender, EventArgs e) { // NoOp } public event EventHandler BEvent; } internal class Program { private static void Main(string[] args) { B b = new B(); WeakReference weakReference = new WeakReference(b); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); bool stillAlive = weakReference.IsAlive; // == false } } 

A and B refer to each other implicitly through events, but GC can delete them (because it does not use reference counting, but mark-and-sweep).

But now consider this example:

 using System; using System.Timers; public class C { private readonly Timer timer; public C() { timer = new Timer(1000); timer.Elapsed += timer_Elapsed; timer.Start(); // (*) } private void timer_Elapsed(object sender, ElapsedEventArgs e) { // NoOp } } internal class Program { private static void Main(string[] args) { C c = new C(); WeakReference weakReference = new WeakReference(c); c = null; GC.Collect(); GC.WaitForPendingFinalizers(); bool stillAlive = weakReference.IsAlive; // == true ! } } 

Why doesn't the GC delete the C object? Why does the timer keep the object alive? Is the timer saved by some kind of "hidden" link of the timer mechanics (for example, by a static link)?

(*) Note: if the timer is created, it does not start, the problem does not occur. If it was started and later stopped, but the event handler was not canceled, the problem persists.

+4
source share
6 answers

Timer logic uses OS functionality. In fact, the OS fires an event. OS, in turn, uses processor interrupts to implement it.

The OS API, also known as Win32, does not contain references to any objects. It contains the memory addresses of the functions that it should call when a timer event occurs..NET GC is not able to track such "links". As a result, the timer object could be collected without canceling the low level event subscription. This is a problem because the OS will try to call it anyway and will work with some strange exception to memory access. Therefore, the .NET Framework stores all such timer objects in an object that statically refers to it and removes them from this collection only after unsubscribing.

If you look at the root of your object using SOS.dll, you will get the following image:

 !GCRoot 022d23fc HandleTable: 001813fc (pinned handle) -> 032d1010 System.Object[] -> 022d2528 System.Threading.TimerQueue -> 022d249c System.Threading.TimerQueueTimer -> 022d2440 System.Threading.TimerCallback -> 022d2408 System.Timers.Timer -> 022d2460 System.Timers.ElapsedEventHandler -> 022d23fc TimerTest.C 

Then, if you look at the System.Threading.TimerQueue class in something like dotPeek, you will see that it is implemented as a singleton and contains a set of timers.

How it works. Unfortunately, the MSDN documentation is not very clear. They simply assumed that if it implements IDisposable, then you should dispose of it, and the question was not asked.

+4
source

Is the timer stored by any "hidden" link of the timer mechanics (for example, by a static link)?

Yes. It is built in the CLR environment, you can see its trace when using a link source or decompiler, a private cookie field in the Timer class. It is passed as the second argument to the constructor of System.Threading.Timer, which actually implements a timer, a state object.

The CLR maintains a list of enabled system timers and adds a reference to the status object to ensure that it does not receive garbage collection. This, in turn, ensures that the Timer object does not receive garbage collection while it is in the list.

Thus, to get collected System.Timers.Timer garbage, you need to call its Stop () method or set its Enabled property to false, the same. This causes the CLR to remove the system timer from the list of active timers. Which also removes the reference to the state object. This then makes the timer object suitable for collection.

Obviously, this is desirable behavior, you usually do not want the timer to simply disappear and stop ticking while it is active. What happens if you use System.Threading.Timer, it stops calling it a callback if you do not store a reference to it, explicitly or using a state object.

+3
source

I think this is due to the way the timer works. When you call Timer.Start (), it sets Timer.Enabled = true. Take a look at the implementation of Timer.Enabled:

 public bool Enabled { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.enabled; } set { if (base.DesignMode) { this.delayedEnable = value; this.enabled = value; } else if (this.initializing) { this.delayedEnable = value; } else if (this.enabled != value) { if (!value) { if (this.timer != null) { this.cookie = null; this.timer.Dispose(); this.timer = null; } this.enabled = value; } else { this.enabled = value; if (this.timer == null) { if (this.disposed) { throw new ObjectDisposedException(base.GetType().Name); } int dueTime = (int) Math.Ceiling(this.interval); this.cookie = new object(); this.timer = new Timer(this.callback, this.cookie, dueTime, this.autoReset ? dueTime : 0xffffffff); } else { this.UpdateTimer(); } } } } } 

It looks like a new timer is being created with the cookie passed to it (very strange!). Following this call path leads to some other complex code, including creating a TimerHolder and a TimerQueueTimer. I expect that at some point a link will be created outside of the timer itself, until you call Timer.Stop () or Timer.Enabled = false.

This is not the final answer, since none of the codes I wrote creates such a link; but he got complicated enough in his guts to make me suspect something like this was happening.

If you have a reflector (or similar), take a look and you will understand what I mean. :)

+2
source

Because Timer is still active. (The event handler is not deleted for Timer.Elapsed ).

If you want to dispose correctly, run the IDisposable interface, remove the event handler in the Dispose method, and use using block or call Dispose manually. The problem will not be.

Example

  public class C : IDisposable { ... void Dispose() { timer.Elapsed -= timer_elapsed; } } 

And then

  C c = new C(); WeakReference weakReference = new WeakReference(c); c.Dispose(); c = null; 
+1
source

I think the problem comes from this line;

 c = null; 

In general, most developers believe that creating an object equal to zero results in the removal of the object by the garbage collector. But this is not so; in fact, only the reference to the memory cell is deleted (where c the object is created); if there are other references to the corresponding memory location, the object will not be marked for deletion. In this case, since Timer refers to the corresponding memory location, the object will not be deleted by the garbage collector.

0
source

Let me first talk about Threading.Timer. The internal timer will build the TimerQueueTimer object using the callback and the state passed to the ctor timer (for example, the new Threading.Timer (callback, state, xxx, xxx). TimerQueueTimer will be added to the static list.

If the callback method and state do not have β€œthis” information (say, using the static method for callback and null for state), then the Timer object can be GCed when there is no reference. On the other hand, if a member method is used for the callback, the delegate containing "this" will be stored in the above static list. Thus, the Timer object cannot be GCed, since the "C" object (in your example) is still referenced.

Now back to System.Timers.Timer, which internally completes Threading.Timer. Note that when the former builds the latter, the member.Timers.Timer method is used, so the System.Timers.Timer object cannot be GCed.

0
source

All Articles