views:

199

answers:

8

Hi,

Please see the code below. I expect it to print either 10 because I have explicitly invoked the garbage collector. But I always get either a 0 or 20 as output. Why is that?

void Main()
{
    Panda[] forest_panda = new Panda[10];
    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    System.GC.Collect();

    Console.WriteLine("Total Pandas created is {0}",Panda.population);          
}

class Panda
{
    public static int population=0;
    public string name;

    public Panda(string name)
    {
        this.name = name;
        population = population + 1;
    }

    ~Panda()
    {
        population = population - 1;
    }   
}

Please note that the class for Main is automatically created by LINQPad (the editor that comes with the "C# 4.0 in a Nutshell" book). I am new to C#.

+3  A: 

Try adding System.GC.WaitForPendingFinalizers after you garbage collect.
http://www.developer.com/net/csharp/article.php/3343191/C-Tip-Forcing-Garbage-Collection-in-NET.htm

muddybruin
thanks that worked...
Manoj
@Manoj: Don't forget to mark this as the accepted/correct answer!
Alastair Pitts
+3  A: 

You have not run an explict garbage collection. From the docs of GC.Collect():

Use this method to attempt to reclaim all memory that is inaccessible. However, the Collect method does not guarantee that all inaccessible memory is reclaimed.

All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to attempt to reclaim the maximum amount of available memory.

The garabage collector is highly optimized and "decides" all by himself when he actually does the garbage collection and then call the finalizers. Additionally it is all done asynchronously. That is also why Finalizers are called non-deterministic cleanup. You never now when cleanup happens.

You have two options now. You can either call GC.WaitForPendingFinalizers() wich will halt the current thread until the all finalizable objects have been finalized. Or call this new overload: System.GC.Collect(int generation, System.GCCollectionMode mode) with GCCollectionMode.Forced It was introduced in .NET 3.5.

Just keep in mind that usually it is not necessary and more importantly: a bad idea to call the garbage collector manually. Also implementing the finalizer is only needed in rare occasions. Calling the garbage collector will slow down the runtime. Implementing finalizers will slow down the runtime additionally. The garabge collector puts all objects that implement the finalizer into the finalization queue when they are ready to be garabge collected. Processing this queue is expensive. To make things worse, when the finalizer is run, it is not guaranteed that the members you are trying to access there are still alive. It is very well possible they have already been grabage collected. That's why you should use the finalizer only when you have unmanaged resources that need to be cleaned up.

All this is definately not needed in your example. What you acutally want is IDisposable for deterministic cleanup.

bitbonk
Because sometime the garbage collector "decided" that it is a good time for garbage collection and luckily all pending finalizer had already been executed.
bitbonk
thanks that worked
Manoj
+2  A: 

You create twenty objects, so the value would then be 20. Explicitly calling System.GC.Collect() does not actually guarantee calling the destructor. Therefore if it was called all 20 objects may have been destructed or none may have been.

This explains what is actually happening.

It isn't good practice to create a destructor or call GC.Collect explicitly.

If An object needs to do cleanup, it should implement IDisposable

Tim Carter
+5  A: 

There are a couple of things to notice here:

First of all the GC behaves differently between release and debug builds. Generally, in release mode objects can be reclaimed sooner than in debug mode.

As Tim points out calling GC.Collect doesn't call finalizers. If you want to wait for finalizers to run call GC.WaitForPendingFinalizers as well.

Finalizers are run by a dedicated thread, so you're actually modifying state from two different threads without any synchronization. While this may not be a problem in this particular case, doing so is not a good idea. But before you go and add synchronization to your finalizer, please keep in mind that a deadlocked finalizer means that no more finalizers will run and thus the memory for those objects will not be reclaimed.

Brian Rasmussen
I never knew there can be a synchronisation issues here! Threads without me creating anything! Is it an issue only when using the finalizer or I have to look out for them whenever using static variables?
Manoj
The issue in this case is because finalizers are run by a dedicated thread created by the runtime.
Brian Rasmussen
+3  A: 

.NET can detect that none of the Panda objects (or the forest_panda array itself) are being referenced after the closing brace before GC.Collect, so the objects may be collected at any time after that line is executed.

Finalizable objects (i.e., classes that declare a ~Class() member) are kept alive after a regular garbage collection so that the Finalizer thread can run their Finalize method later; one call to GC.Collect is not enough to ensure that these objects are removed from memory. (In fact, finalizable objects will get promoted to the next generation, so multiple generation 0 collections may not free them; see MSDN for more information.)

So, if one collection (that has moved these objects to the "freachable queue") has happened already, you will have zero Panda objects, because the GC.Collect you explicitly invoke will have the side-effect of running the Panda finalizer. If no collections have happened already, all 20 Panda objects will be registered on the finalization queue and will be finalized at the next garbage collection.

On a side note, the Panda.population variable is not thread-safe; you need to use a lock or Interlocked.Increment.

On another side note, since .NET 2.0, writing a finalizer has been considered a bad idea; see Joe Duffy's blog for more information on this.

Bradley Grainger
I never knew there can be a synchronisation issues here! Is it an issue only when using the finalizer or I have to look out for them whenever using static variables?
Manoj
There are potential synchronisation issues any time you use a static variable, if it is possible that that variable could be used on two different threads. In .NET the finalizer always runs on its own dedicated thread.
Bradley Grainger
+1  A: 

In .NET, object lifetimes are non-deterministic and do not behave as you'd expect from C++ constructor/destructors. In fact, .NET objects do not technically have destructors. The finalizer differs in that it is expected to clean up unmanaged resources used by the object during it's lifetime.

To have a deterministic way of freeing resources used by your object, you implement the IDisposable interface. IDisposable isn't perfect though as it still requires the calling code to correctly dispose of the object when it's done, and it's hard to handle accidental multiple calls to Dispose. However syntax in C# makes this generally very easy.

class Panda : IDisposable
{
    public static int population = 0;
    public string _name;

    public Panda( string name )
    {
        if( name == null )
            throw new ArgumentNullException( name );

        _name = name;
        population++;
    }

    protected virtual void Dispose( bool disposing )
    {
        if( disposing && name != null )
        {
            population--;
            name = null;
        }
    }

    public void Dispose()
    {
        Dispose( true );
        GC.SuppressFinalize( this );
    }

    ~Panda(){ Dispose( false ); }
}

Then to use the class:

using( var panda = new Panda( "Cute & Cuddly" ) )
{
    // Do something with the panda

} // panda.Dispose() called automatically
Paul Alexander
Yeah I was thinking along the lines of C++ destructor and so was confused. Thanks..
Manoj
+1  A: 

Using destructors (a.k.a. finalizers) is not really a good way of doing things in C#. There is no guarantee that the finalizer will ever run, even if you explicitly invoke the garbage collector. You shouldn't try to force garbage collection either, because it will probably have a negative perfomance impact on your application overall.

Instead, if you need to explicitly free resources owned by an object, you should implement the IDisposable interface, and place your cleanup logic inside the Dispose() method. Conversely, when you use an object that implements IDisposable, you should always take care to call its Dispose() method when you are finished with it. C# provides the "using" statement for this purpose.

Many classes that do I/O (such as Streams) implement IDisposable. Here is an example of using a FileStream to read a text file. Note the "using" statement to ensure the FileStream is disposed when we are finished with it:

using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
} // Dispose() is called automatically here

The above code is equivalent to this:

FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
try
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
}
finally
{
    fs.Dispose();
}
Brian
+1  A: 

The Disposing Pattern would be the best to use. Here's the full implementation of your work.

Remember, that you have to call Dispose by your own like done in the code below.

    public static void Main()
    {
        Panda[] forest_panda = new Panda[10];
        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();


        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();

        Console.WriteLine("Total Pandas created is {0}", Panda.population);
    }

    class Panda : IDisposable
    {
        public static int population = 0;
        public string name;

        public Panda(string name)
        {
            this.name = name;
            population = population + 1;
        }

        ~Panda()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                population = population - 1;
            }
        }
    }
BitKFu