locked
GC does not work in Console Application with STAThread specified. RRS feed

  • Question

  • I have a host that is a Console application. To exit the host the user has to press enter.
     I ran into what I thought was a remoting memory leak . In the
    end, the garbage collector was running but was unable to collect objects
    created on main thread as I had it blocked by Console.ReadLine() to prevent
    the console window from closing.

    The solution was to change the Main method attribute from [STAThread] to
    [MTAThread] which allows the garbage collector threads into the apartment -
    all memory leaks then ceased without changing another single thing.

    Why was this happening? You will not believe that the garbage collector was not working at all. There was a huge memory consumtion and even the handle count was increasing like hell and the host crashed in a few hours. I was finally receiving an OUT OF MEMORY exception. Simply by changing the STAThread to MTAThread , my host has now been running successfully for a week now and there is no leak at all. Does anyone has any explanation for this? Shouldn't Microsoft publish a list of such issues and their tweaks so that developer's don't have to waste time.
    I'll also like to mention that with this, all sockets that are being created are now closed properly.
    Monday, June 6, 2005 9:50 AM

Answers

  • Hi Akhil,

    When you host COM or Serviced components inside a STA thread, the finalizer thread must reenter the STA thread in order to finalize the component. If the STA is blocked and isn't pumping, the finalizer has to wait in line until it does. In the case of ReadLine(), it's just sitting there waiting for user input. It pumps when it first enters a blocking state, but after that you're hosed. Chris Brumme writes about this at http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx; caution: that's a fairly lengthy post.

    I am wondering: What type of components were being leaked? It could be that the finalizer was blocked on just one STA component, and that a whole bunch of other stuff was backed up behind it. Unfortunately, the finalizer isn't going to skip your component in search of free-threaded components that it can make progress on.

    To get around this issue, you have some options (from best to worst), e.g.:

    1) Create your components in an MTA. You figured this out. Unless you have an explicit reason to use an STA, you shouldn't. I realize that Visual Studio adds these to some entrypoints automatically for you. For example, most GUI applications have to start life inside an STA, e.g. WinForms, but Console applications certainly do not.

    2) Deterministically release your resources. If you are using components which implement IDisposable, wrap them in a C# 'using' statement or call Dispose() on them explicitly when you're done. RCW's done have Dispose on them. You can consider doing a Marshal.ReleaseComObject on them directly, but realize that this can cause problems if you're not really done using the COM object.

    3) Use another form of blocking to prevent the primary thread from exiting. Not knowing what condition should trigger the exit, it's hard to make a recommendation. You might consider using a WaitHandle that gets signaled when it's time to exit. For instance, the main loop does this:

    EventWaitHandle wh = new ManualResetEvent(false);

    /* Main thread */
    Thread t = new Thread(delegate() {
        Console.ReadLine();
        wh.Set();
    });
    t.Start();
    // do whatever it is the main thread does
    while (!wh.WaitOne(100, false)); // prevent main thread from exiting

    This last one is not a great solution. The WaitOne loop might be too tight or loose (timeout) depending on how many finalizable objects there are. But when it blocks it pumps, so it means that your finalizer won't get completely backed up.

    I hope this helps.

    Regards,
    Joe Duffy (joedu@microsoft.com)
    Program Manager - CLR Team
    Microsoft

    Monday, August 22, 2005 6:19 PM

All replies

  • Hi Akhil,

    When you host COM or Serviced components inside a STA thread, the finalizer thread must reenter the STA thread in order to finalize the component. If the STA is blocked and isn't pumping, the finalizer has to wait in line until it does. In the case of ReadLine(), it's just sitting there waiting for user input. It pumps when it first enters a blocking state, but after that you're hosed. Chris Brumme writes about this at http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx; caution: that's a fairly lengthy post.

    I am wondering: What type of components were being leaked? It could be that the finalizer was blocked on just one STA component, and that a whole bunch of other stuff was backed up behind it. Unfortunately, the finalizer isn't going to skip your component in search of free-threaded components that it can make progress on.

    To get around this issue, you have some options (from best to worst), e.g.:

    1) Create your components in an MTA. You figured this out. Unless you have an explicit reason to use an STA, you shouldn't. I realize that Visual Studio adds these to some entrypoints automatically for you. For example, most GUI applications have to start life inside an STA, e.g. WinForms, but Console applications certainly do not.

    2) Deterministically release your resources. If you are using components which implement IDisposable, wrap them in a C# 'using' statement or call Dispose() on them explicitly when you're done. RCW's done have Dispose on them. You can consider doing a Marshal.ReleaseComObject on them directly, but realize that this can cause problems if you're not really done using the COM object.

    3) Use another form of blocking to prevent the primary thread from exiting. Not knowing what condition should trigger the exit, it's hard to make a recommendation. You might consider using a WaitHandle that gets signaled when it's time to exit. For instance, the main loop does this:

    EventWaitHandle wh = new ManualResetEvent(false);

    /* Main thread */
    Thread t = new Thread(delegate() {
        Console.ReadLine();
        wh.Set();
    });
    t.Start();
    // do whatever it is the main thread does
    while (!wh.WaitOne(100, false)); // prevent main thread from exiting

    This last one is not a great solution. The WaitOne loop might be too tight or loose (timeout) depending on how many finalizable objects there are. But when it blocks it pumps, so it means that your finalizer won't get completely backed up.

    I hope this helps.

    Regards,
    Joe Duffy (joedu@microsoft.com)
    Program Manager - CLR Team
    Microsoft

    Monday, August 22, 2005 6:19 PM
  • Joe,
      In the last solution WaitOne will pump messages for STA thread. Why loop?

      Also, (I agree with you that the issue is most likely a created STA component) however just changing the thread appartment to MTA could result in performance penalties when used from such an appartment, and therefore wouldn't not be the best solution.

    Monday, August 22, 2005 7:55 PM