Reputation: 680
I have a class which has a System.Timers.Timer property. The timer will start on object creation using a constructor.
Class definition:
public class Temp
{
public int ID { get; set; } = 0;
public System.Timers.Timer Timer { get; set; } = null;
public Temp(int id)
{
ID = id;
Timer = new System.Timers.Timer(5000);
Timer.Elapsed += Timer_Elapsed;
Timer.Enabled = true;
Timer.Start();
Console.Write(this.ID + "Class Constructor");
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.Write(this.ID + "Timer_Elapsed");
}
~Temp()
{
Console.Write(this.ID + "Class Distructor");
}
}
Note:
I am not calling Timer.Stop() method or setting Timer.Enable = false anywhere in the class. It means that the timer will be destroyed only when the destructor is called. I wrote a destructor to know when the class is being destroyed.
Requirement:
I need to stop or destroy the timer when I assign a null value or new instance to the class instance variable. I don't want to handle timer from outside of the class by making it public and implement methods like temp.Dispose() or temp.StopTimer() etc where the timer will be stopped and then assign a new Temp class instance to temp variable.
Code sample for the usage of class:
static class Program
{
static void Main()
{
Temp temp = new Temp(1);
temp = null; // Or line not nacessary.
temp = new Temp(2);
GC.Collect();
GC.WaitForFullGCComplete();
Thread.Sleep(Timeout.Infinite);
}
}
My current understanding or assumptions when the timer will be destroyed:
1. On exit of the program:
Works perfectly. Destructor is called twice instead of once. Not sure if this is normal?
2. On assignment of null to the variable:
I am assuming that when a null value is assigned or new instance is assigned old instance is eligible for garbage collection. So I am expecting a call to the destructor. But it is not getting called. Is there any time or memory limit when the destructor is called or null assignment will not call garbage collector?
3. In c# we can't call the destructor explicitly.
4. Explicitly call garbage collection is not working.
Is there any other way of doing this?
Upvotes: 1
Views: 1207
Reputation: 6060
- On assignment of null to the variable:
I am assuming that when a null value is assigned or new instance is assigned old instance is eligible for garbage collection. So I am expecting a call to the destructor. But it is not getting called. Is there any time or memory limit when the destructor is called or null assignment will not call garbage collector?
Close, but incorrect. When there are no remaining references to the object, which may coincide to when you set your reference to null (if there are no other references, even non-obvious references like something subscribed to one of your object's events), then it becomes eligible to be garbage collected. The finalizer won't be called until it actually is garbage collected, however; not when it becomes eligible. The GC may choose not to collect your object, even if it can. If an object wasn't eligible last time the GC ran, the GC guesses that it won't be eligible the next time, and pushes it to a higher tier. Long-lifetime objects often get pinned until full tier 2 GC runs (which may be never, if memory pressure isn't high enough). As a general rule, if you are relying on the GC to finalize your object at some deterministic time, you are doing something wrong.
I need to stop or destroy the timer when I assign a null value or new instance to the class instance variable
You need to use reference counting, not automatic garbage collection, to meet this requirement. To do this, place your reference in a property of something else, and use that to implicitly track references:
public class MyTimerReference {
private Temp temp = null;
public Temp Value {
get {
return temp;
}
set {
var oldRef = temp;
if (value != null)
value.AddRef();
temp = value;
if (oldRef != null)
oldRef.ReleaseRef();
}
}
}
public class Temp
{
public int ID { get; set; } = 0;
public System.Timers.Timer Timer { get; set; } = null;
public Temp(int id)
{
ID = id;
Timer = new System.Timers.Timer(5000);
Timer.Elapsed += Timer_Elapsed;
Timer.Enabled = true;
Timer.Start();
Console.Write(this.ID + "Class Constructor");
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.Write(this.ID + "Timer_Elapsed");
}
private int refCount = 0;
void AddRef() {
refCount++;
}
void ReleaseRef() {
refCount--;
if (refCount == 0) {
Timer.Stop();
Console.Write(this.ID + "stopped");
}
}
}
Now you can, with an instance of MyTimerReference
do something like this:
myTimerReference.Value = new Temp();
myTimerReference.Value = null; //this will cause the old `Temp` instance to cleanup
Or
var ref1 = new MyTimerReference();
var ref2 = new MyTimerReference();
ref1.Value = new Temp(); //timer gets started
ref2.Value = ref1.Value; //nothing happens
ref1.Value = null; //nothing happens
ref2.Value = new Temp(); //first timer stops, second timer starts
ref2.Value = null; //second timer stops
Upvotes: 1