none
GC finalizes local Variable before leaving function RRS feed

  • Question

  • Hi,

    the following program crashes after a few iterations with a "Collection was modified; Enumeration operation may not execute.".

    using System;
    using System.Collections.Generic;
    
    namespace Test
    {
      class Program
      {
        static void Main(string[] args)
        {
          Owner myOwner = new Owner();
          
          foreach (string text in myOwner.myLongList)
          {
            Console.WriteLine(text);
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
          }
    
          // myOwner.DontDiePlease();
        }
      }
    
      class Owner
      {
        public List<string> myLongList;
    
        public Owner()
        {
          myLongList = new List<string>();
          for (int i = 0; i < 20000; i++)
            myLongList.Add("string " + i.ToString());
        }
    
        ~Owner()
        {
          Console.WriteLine("FINALIZED");
          myLongList.RemoveAt(0);
        }
    
        public void DontDiePlease()
        {
          Console.WriteLine("I won't");
        }
      }
    }
    

    The crash only happens when running the Release binaries without an debugger attached, in .Net 4.0 and prior versions. Uncommenting myOwner.DontDiePlease(); will prevent the crash.

    The crash does also happen when GC.Collect is removed and the program does consume enough of memory. In the real project, where we first stumbled over this problem, the collection is cleaned up in ~finalizer/Dispose because it references some unmanaged resources.

    I would expect myOwner to be kept by the GC until the variable runs out of scope. This behavior can lead to random crashes in our application.

    What do you think?
    Thanks & kind regards,

    Julian

    Friday, December 10, 2010 4:48 PM

Answers

  • Hi Julian,

    the issue is that Owner is only needed during the first run of your for loop to get the LongList out of it. Foreach will call on the list GetEnumerator and use the enumerator object from now on. The object that containing the list is no longer needed and can be garbage collected. The only guarantee you currently have is that you List<string> won´t go out of scope unitl the last element was reached.

    If this is an issue you could create derived List<string> object which does take the container as private reference field in its ctor to ensure that your container is kept alive as long as the list is needed.

     

    class ContainerList<T> : List<T>
    {
    
        object myContainer;
        public ContainerList(object container)
        {
          myContainer = container;
       }
    }
    
    
    

     

    Yours,

        Alois Kraus

     

    • Marked as answer by Cookie Luo Friday, December 17, 2010 2:09 AM
    Saturday, December 11, 2010 1:07 PM

All replies

  • The GC appears to be collecting myOwner during the loop when you are running in Release mode. It's something to do with GC optimizations when running the release version of the code. There's an example a bit like it here .

    The additional call to DontDiePlease is causing the GC to wait and dispose of myOwner after the loop. Things that also work are implementing the Using/Dispose pattern instead of using a finalizer or converting the foreach to a for (presumably because there's will be a reference to myOwner inside the for loop).

    I can't quite work out whether you've found a bug or not ....

    Friday, December 10, 2010 6:46 PM
  • Hi Julian,

    the issue is that Owner is only needed during the first run of your for loop to get the LongList out of it. Foreach will call on the list GetEnumerator and use the enumerator object from now on. The object that containing the list is no longer needed and can be garbage collected. The only guarantee you currently have is that you List<string> won´t go out of scope unitl the last element was reached.

    If this is an issue you could create derived List<string> object which does take the container as private reference field in its ctor to ensure that your container is kept alive as long as the list is needed.

     

    class ContainerList<T> : List<T>
    {
    
        object myContainer;
        public ContainerList(object container)
        {
          myContainer = container;
       }
    }
    
    
    

     

    Yours,

        Alois Kraus

     

    • Marked as answer by Cookie Luo Friday, December 17, 2010 2:09 AM
    Saturday, December 11, 2010 1:07 PM
  • Thank you. There are many solutions for this problem.

    My main problem is that this error only occure in Release mode when the memory consumption is pretty high and the loop is long enough, which is something I would expect not to happen in .Net. There is no security that I find all places/classes where this could happen and that nobody of my colleagues throws something in again. Deactivating optimization does not solve this.

    I would expect the scope to end at the end of the block it was defined in all the time or at least when a finalizer is defined. In my case keeping it alive until the end of the loop would be enough, but John's example would still "fail".

    Julian

    Monday, December 13, 2010 9:04 AM
  • I think the official solution to this problem is to put the call: System.GC.StayAlive(myOwner) at the end of the method.

    Brad Pechacek
    Monday, December 13, 2010 6:28 PM
  • It's GC.KeepAlive() - it's just no-op syntactic sugar so there's a reference to the object in the code.

    "I would expect myOwner to be kept by the GC until the variable runs out of scope. " Scope has nothing to do with GC in C#.

    It's vaguely possible that Console.WriteLine might be something to do with this, just speculation. If the string gets into unmanaged code there's a point at which it may appear that there are no longer any references to the Owner object. I'd be curious if you get the same error doing something a bit more managed, like writing to a FileStream.


    Phil Wilson
    Thursday, December 16, 2010 12:51 AM
  • It would work if Owner would implement IDisposable and in  program create the object in using statement
    Thursday, December 16, 2010 3:43 PM