none
Does not calling EndInvoke *really* cause a memory leak ?

    Question

  • This is an old cookie, but I cannot locate a definitive answer.

     

    If I use a delegate to invoke a method asyncronously, and I never call EndInvoke (because I don't care about the return value), do I leak memory ?

     

    Some pretty  knowledgeable people have debated this and changed thier minds.

    e.g. http://www.pluralsight.com/blogs/dbox/archive/2005/05/23/8529.aspx

     

    I should say that I have conducted some tests on this an I cannot prove that a memory leak occurs, in fact, if you call a method asyncronously and discard all (hard) references to the IAsyncResult, then track it with a WeakReference, you can actually see it being collected correctly as soon as the Async method completes.

     

    Any thoughts?

     

     

     

    Monday, October 08, 2007 9:51 AM

Answers

  • It can but it won't necessarily.  Technically there is no such thing as a memory leak in .NET.  Eventually the memory will be reclaimed by the GC.  The problem is that it might be around a long time. 

    The reason that you should call EndInvoke
    is because the results of the invocation (even if there is no return value) must be cached by .NET until EndInvoke is called.  For example if the invoked code throws an exception then the exception is cached in the invocation data.  Until you call EndInvoke it remains in memory.  After you call EndInvoke the memory can be released.  For this particular case it is possible the memory will remain until the process shuts down because the data is maintained internally by the invocation code.  I guess the GC might eventually collect it but I don't know how the GC would know that you have abandoned the data vs. just taking a really long time to retrieve it.  I doubt it does.  Hence a memory leak can occur.

     

    One of the issues with this argument is that BeginInvoke is implemented differently depending upon how you call it.  The implementation from Control is different than the implementation of Delegate.  Therefore it is hard to say that it will or will not leak memory.  It depends on the circumstances.

     

    I doubt you'll find anybody who will go on record saying that BI will not leak memory under any circumstance.  Instead you'll find that people will say that in a particular case BI will not leak or if you use a particular version of BI then it won't leak.  Unless you know these special cases it is just better/safer to assume it can leak and always call EI.

     

    Here's a simple program for you to play around with.  When run the non-EI version takes about 2MB of memory while the EI version takes 600K on my machine.  If you enable the throwing of the exception then the memory usage changes.  At least on my machine it takes more memory if you do call EI (by about a MB).  This is probably because it creates a new exception to wrap the thrown exception when you call EI

     

    Code Block

    class Program

    {

       public delegate void DoNothingDelegate();

       public static void ThrowException()

       {

          //throw new Exception("Oops");

       }

       static void Main(string[] args)

       {

          DoNothingDelegate del = new DoNothingDelegate(ThrowException);

          long nStartMemory = Process.GetCurrentProcess().WorkingSet64;

          for (int nIdx = 0; nIdx < 1000; ++nIdx)

          {

             del.BeginInvoke(null, null);

          };

          long nEndMemory = Process.GetCurrentProcess().WorkingSet64;

          Console.WriteLine("Memory (no EndInvoke)- Starting: {0:N}, Ending: {1:N}"nStartMemory, nEndMemory);

      

          del = new DoNothingDelegate(ThrowException);

          nStartMemory = Process.GetCurrentProcess().WorkingSet64;

          for (int nIdx = 0; nIdx < 1000; ++nIdx)

          {

             IAsyncResult results = del.BeginInvoke(null, null);

             try

             {

                del.EndInvoke(results);

             } catch (Exception e)

             { };

             results = null;

          };

          nEndMemory = Process.GetCurrentProcess().WorkingSet64;

          Console.WriteLine("Memory (EndInvoke)- Starting: {0:N}, Ending: {1:N}", nStartMemory, nEndMemory);

       }

    }

     

    It is scientific by no means but it gives you an ideal of the impact (at least in this case) of calling EI or not.

     

    Michael Taylor - 10/8/07

    http://p3net.mvps.org

    Monday, October 08, 2007 1:38 PM
  • Most of the implementation of delegates is in unmanaged code.  Therefore the GC doesn't apply.  The base delegate object is managed but all the support infrastructure resides in unmanaged code.  A quick walkthrough of the code reveals that it does quite a bit of work to manage the handshaking.  However since most of the code is dynamically generated it is hard to say.  You can look at AsyncResult which is probably the class that ultimately probably handles the end processing.  Within the class you can see where it moves data around and propogates things back to the main thread's context (which is not going to be released anytime soon).

     

    You should go back to the original meaning of "memory leak" though.  A memory leak occurs when memory is lost in the system (meaning inaccessible) and is inaccessible for the duration of the process.  In .NET a true memory leak doesn't occur in managed code.  So even allocating a huge array of reference types and then doing nothing will not result in a memory leak as it will eventually get cleaned up.  With .NET the concern is more about releasing memory/resources when they are no longer needed.  In this case not calling EndInvoke is effectively a memory leak as the memory will be lost until the GC eventually runs.  It will not be cleaned up when you no longer need it as it should be.  Additionally calling GC.Collect would do more harm than good so calling it in lieu of EndInvoke is not an option. 

     

    Ultimately though, IMHO, why take the chance that memory will be "leaked" if you don't call EndInvoke.  Call it and sleep better at night.  The only justification I've ever heard about not calling it revolves around the fact that people don't want to have to worry about tracking it.  I find this to be a poor excuse.  None of my devs would be allowed to get away with it as it is, IMO, sloppy code.  It is no different than leaving a file open.  That's just me though.

     

    Michael Taylor - 10/8/07

    http://p3net.mvps.org

     

     

     

    Monday, October 08, 2007 3:40 PM

All replies

  • It can but it won't necessarily.  Technically there is no such thing as a memory leak in .NET.  Eventually the memory will be reclaimed by the GC.  The problem is that it might be around a long time. 

    The reason that you should call EndInvoke
    is because the results of the invocation (even if there is no return value) must be cached by .NET until EndInvoke is called.  For example if the invoked code throws an exception then the exception is cached in the invocation data.  Until you call EndInvoke it remains in memory.  After you call EndInvoke the memory can be released.  For this particular case it is possible the memory will remain until the process shuts down because the data is maintained internally by the invocation code.  I guess the GC might eventually collect it but I don't know how the GC would know that you have abandoned the data vs. just taking a really long time to retrieve it.  I doubt it does.  Hence a memory leak can occur.

     

    One of the issues with this argument is that BeginInvoke is implemented differently depending upon how you call it.  The implementation from Control is different than the implementation of Delegate.  Therefore it is hard to say that it will or will not leak memory.  It depends on the circumstances.

     

    I doubt you'll find anybody who will go on record saying that BI will not leak memory under any circumstance.  Instead you'll find that people will say that in a particular case BI will not leak or if you use a particular version of BI then it won't leak.  Unless you know these special cases it is just better/safer to assume it can leak and always call EI.

     

    Here's a simple program for you to play around with.  When run the non-EI version takes about 2MB of memory while the EI version takes 600K on my machine.  If you enable the throwing of the exception then the memory usage changes.  At least on my machine it takes more memory if you do call EI (by about a MB).  This is probably because it creates a new exception to wrap the thrown exception when you call EI

     

    Code Block

    class Program

    {

       public delegate void DoNothingDelegate();

       public static void ThrowException()

       {

          //throw new Exception("Oops");

       }

       static void Main(string[] args)

       {

          DoNothingDelegate del = new DoNothingDelegate(ThrowException);

          long nStartMemory = Process.GetCurrentProcess().WorkingSet64;

          for (int nIdx = 0; nIdx < 1000; ++nIdx)

          {

             del.BeginInvoke(null, null);

          };

          long nEndMemory = Process.GetCurrentProcess().WorkingSet64;

          Console.WriteLine("Memory (no EndInvoke)- Starting: {0:N}, Ending: {1:N}"nStartMemory, nEndMemory);

      

          del = new DoNothingDelegate(ThrowException);

          nStartMemory = Process.GetCurrentProcess().WorkingSet64;

          for (int nIdx = 0; nIdx < 1000; ++nIdx)

          {

             IAsyncResult results = del.BeginInvoke(null, null);

             try

             {

                del.EndInvoke(results);

             } catch (Exception e)

             { };

             results = null;

          };

          nEndMemory = Process.GetCurrentProcess().WorkingSet64;

          Console.WriteLine("Memory (EndInvoke)- Starting: {0:N}, Ending: {1:N}", nStartMemory, nEndMemory);

       }

    }

     

    It is scientific by no means but it gives you an ideal of the impact (at least in this case) of calling EI or not.

     

    Michael Taylor - 10/8/07

    http://p3net.mvps.org

    Monday, October 08, 2007 1:38 PM
  • I am aware of the implementation in Control, but I specifically tested a plain static method called async via a delegate.

    A lot of blogs describe a vague scenario whereby the runtime keeps the AsyncResult object alive 'in case'  EndInvoke is called sometime later. I don't subscribe to this explanation, it is not complete. As long as there are no references to an AsyncResult object, it is garbage just as any other object would be and can be (and is) collected.

     

    My code is below, I explicitly release all references to the AsyncResult, and track it with a WeakReference. It always gets collected right after the async method completes (by the next forced GC). You can even observe the return object/exception being finalized.

     

    It's also important to force GC.Collect to make the test valid. If you don't do this code that calls/doesn't call EndInvoke may well behave differently, but this could just be the non-deterministic behavior of the GC.

    Watching the memory load of the CLR is an imprecise way to determine what is going on.

     

    In fact when I tested millions of repeated async calls with no EndInvoke, it did consume more memory than with the call to EndInvoke, but it did not grow indefinitley, this is why  coded the test below.

     

    I'm not going to claim I'm 100% certain not calling EndInvoke is not a problem of somekind, but am I yet to see a complete explanation of the mechanism by which AsyncResults pile up until process exit. (What exactly is maintaining a reference to them and why).

     

     

    Code Block

    /*
     *  This sample demonstrates the behavior of Async delegates in 'fire and forget' mode (i.e. never calling EndInvoke).
     *  There has been some blogtalk on potential memory leaks when EndInvoke is not called, with the suggestion that the
     *  AsyncResult object somehow does not get released and sticks long around after the call completes.
     *  Some go as far as saying that you *must* always call EndInvoke, or supply an Async callback to call it for you.
     *
     *  This demo code is intended to shed some light on the real behaviour.
     *
     *  The code does the following
     *   1. Invokes a method asynchronously with a delegate (the method returns a reference type which has a finalizer).
     *   2. Creates a WeakReference to the AsyncResult object, in order to track it's garbage collection.
     *   3. Explicitly discards (by setting the hard reference to null), the AsyncResult object.
     *   4. Never calls EndInvoke.
     *   5. Watches the WeakReference to see if the AsyncObject is indeed collected.
     *   6. Forces regular GC.Collect's.
     *
     *  The results are interesting.
     *   1. As long as the code invoking the async method does not hold a reference to the AsyncResult, it is indeed collected
     *      right after the async method completes (i.e at the next GC).
     *   2. The return object (which is presumably transferred onto the AsyncResult object) is also collected
     *      (indicated by it's finalizer getting called).
     *   3. An exception, if thrown by the async method (which is also presumably transferred onto the AsyncResult object)
     *      is also collected (indicated by it's finalizer getting called).
     *
     *  This all looks to me like the CLR is cleaning everything up properly, even without the EndInvoke call.
     *
     *  What I think is going on....
     *   For an object not to be garbage, it must have a reference held to it from somewhere. There are two possible sources for
     *   references to an AsyncResult
     *    1. The code that called BeginInvoke (it can optionally keep a reference so that it can call EndInvoke later).
     *    2. The thread that runs the asynchronously invoked method
     *       (it has to keep a reference so it can transfer the return value onto it, or transfer any raised exception onto it)
     *   
     *    Once the thread completes the async method, the 2nd reference can be released, presumably the thread returns to the pool and
     *    picks up a new AsyncResult when it is recycled for another async call.
     *    So when the call completes, if there is no reference froom anywhere else, the AsyncResult object can be collected.
     *
     *   Assertions that the ThreadPool/AsyncThread keeps a reference to the AsyncResult object 'in case' someone
     *   calls EndInvoke are not logical. If no other references to the AsyncResult exist how can anyone call EndInvoke !
     *
     *   To change the behavior, set the flags and values at the start of Main() and run.
     *    The int 'runfor' controls how long the async method runs for in seconds.
     *    The boolean flag 'callend' controls whether the EndInvoke is called or not (set to false).
     *    The boolean flag 'throwex' controls whether the async method returns normally or throws an exception
     *
     *      Mark.Billingham@ics.net
     *      Oct 2007.
     */


    using System;
    using System.Runtime.InteropServices;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.Remoting.Messaging;

    namespace EndInvokeLeakTestConsole
    {
        class ReturnType
        {
            private string m_result;
            private int m_code;

            public ReturnType(string result, int code)
            {
                m_result = result;
                m_code = code;
                Console.WriteLine(" ReturnType constructed");
            }

            public string Result { get { return m_result; } }
           
            ~ReturnType()
            {
                Console.WriteLine(" ReturnType finalized");
            }
        }


        class CustomException : ApplicationException
        {
            public CustomException(string msg) : base(msg)
            {
                Console.WriteLine(" CustomException constructed");
            }

            ~CustomException()
            {
                Console.WriteLine(" CustomException finalized");
            }
        }


        class Program
        {
            // Get the REAL thread ID
            [DllImport("kernel32.dll")]
            static extern uint GetCurrentThreadId();
           
            // Delegate used for async invokation.
            private delegate ReturnType AsyncMethodDelegate(int runfor, bool throwex);
           
           
           
            static void Main(string[] args)
            {
                // Set these 3 to change behavior.
                int runfor = 10;
                bool callend = false;
                bool throwex = false;

                Console.WriteLine("{0} : 0x{1:X8} : Main() Running", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                AsyncMethodDelegate begdel = new AsyncMethodDelegate(AsyncMethod);

                IAsyncResult iar = begdel.BeginInvoke(runfor, throwex, null, null);

                WeakReference wr = new WeakReference(iar);
                if(!callend)
                    iar = null;

                // Watch it vanish before your very eyes!!!
                for (int i = 0; i < runfor + 5; i++)
                {
                    System.GC.Collect();
                    System.Threading.Thread.Sleep(1000);
                    object target = wr.Target;
                    if(target == null)
                    {
                        Console.WriteLine("{0} : 0x{1:X8} : WeakReference is NULL", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                    }
                    else
                    {
                        Console.WriteLine("{0} : 0x{1:X8} : WeakReference is SOMETHING", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                        //Console.WriteLine("  it's a : {0}",target.GetType().ToString());
                        target = null;
                    }
                }

                if (callend)
                {
                    AsyncResult ar = iar as AsyncResult;
                    AsyncMethodDelegate enddel = ar.AsyncDelegate as AsyncMethodDelegate;
                    try
                    {
                        ReturnType ret = enddel.EndInvoke(iar);
                        Console.WriteLine("{0} : 0x{1:X8} : EndInvoke result = {2}", DateTime.Now.ToLongTimeString(), GetCurrentThreadId(), ret.Result);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("{0} : 0x{1:X8} : Caught Exception from EndInvoke: {2}", DateTime.Now.ToLongTimeString(), GetCurrentThreadId(), ex.Message);
                    }
                }

                Console.WriteLine("{0} : 0x{1:X8} : Main() Completed", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                Console.ReadLine();
            }

            static ReturnType AsyncMethod(int runfor, bool throwex)
            {
                Console.WriteLine("{0} : 0x{1:X8} : AsyncMethod() Running", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                for(int i=0 ; i<runfor ; i++)
                    System.Threading.Thread.Sleep(1000);

                if (throwex)
                {
                    Console.WriteLine("{0} : 0x{1:X8} : AsyncMethod() throws an exception ****", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                    throw new CustomException("Bad Show");
                }

                Console.WriteLine("{0} : 0x{1:X8} : AsyncMethod() Completed", DateTime.Now.ToLongTimeString(), GetCurrentThreadId());
                return new ReturnType("A Result", 101);
            }
        }
    }

     

     

    Monday, October 08, 2007 2:33 PM
  • Most of the implementation of delegates is in unmanaged code.  Therefore the GC doesn't apply.  The base delegate object is managed but all the support infrastructure resides in unmanaged code.  A quick walkthrough of the code reveals that it does quite a bit of work to manage the handshaking.  However since most of the code is dynamically generated it is hard to say.  You can look at AsyncResult which is probably the class that ultimately probably handles the end processing.  Within the class you can see where it moves data around and propogates things back to the main thread's context (which is not going to be released anytime soon).

     

    You should go back to the original meaning of "memory leak" though.  A memory leak occurs when memory is lost in the system (meaning inaccessible) and is inaccessible for the duration of the process.  In .NET a true memory leak doesn't occur in managed code.  So even allocating a huge array of reference types and then doing nothing will not result in a memory leak as it will eventually get cleaned up.  With .NET the concern is more about releasing memory/resources when they are no longer needed.  In this case not calling EndInvoke is effectively a memory leak as the memory will be lost until the GC eventually runs.  It will not be cleaned up when you no longer need it as it should be.  Additionally calling GC.Collect would do more harm than good so calling it in lieu of EndInvoke is not an option. 

     

    Ultimately though, IMHO, why take the chance that memory will be "leaked" if you don't call EndInvoke.  Call it and sleep better at night.  The only justification I've ever heard about not calling it revolves around the fact that people don't want to have to worry about tracking it.  I find this to be a poor excuse.  None of my devs would be allowed to get away with it as it is, IMO, sloppy code.  It is no different than leaving a file open.  That's just me though.

     

    Michael Taylor - 10/8/07

    http://p3net.mvps.org

     

     

     

    Monday, October 08, 2007 3:40 PM
  • I think you have misunderstood the point of me calling GC.Collect, it was not in lieu of EndInvoke, it was simply a way of determining if the AsyncResult object was scheduled for collection in a more deterministic fashion, and it looks like it is. This is not a memory leak, the memory is free to be recovered at any time by the GC.

     

    The behavior of the code is pretty clear, the AsyncResult itself is treated as a regular reference object and garbage collected normally.

     

    I'm not sure I understand this statement

    >>In this case not calling EndInvoke is effectively a memory leak as the memory will be lost until the GC eventually runs.It will not be cleaned up when you no longer need it as it should be.

     

    As long as the GC is free to collect the memory 'eventually' by definition it is not a memory leak. No memory is ever 'cleaned up' until the GC runs. (unless it an unmanaged resource and you call Dispose).

     

    Your analogy with the file handle has prompted another thought though, if you retrieve the WaitHandle and from the AsyncResult in order to wait on the async method with WaitOne, it may be creating a Win32 syncronization primitive which would be an unmanaged resoruce, this presumably would not be released until the AsyncResult is finalized (if indeed it has a finalizer).

    In this case I have read that the sync primitive is only created d if you actually ask for the wait handle, so Calling WaitOne, timing out, then not calling EndInvoke may be the risky pattern.

     

    It is possible that EndInvoke is responsible for releasing the unmanaged syncroniztion primitive (a kind of disguised dispose pattern). In this casen not calling it could lead to a handle leak rather than a memory leak.

    I'll do another test for leaked handles.

     

    I suppose it is also possible that there is some unmanaged memory allocated behind the AsyncResult (thread local storage maybe ?) which needs to be freed explicitly, but I'd need to see the implementation of EndInvoke to be convinced. 

     

    It's not that I don't want to call EndInvoke, if it were my code I'd just call it and discard the results, it's just that I am reviewing some code for threading issues and this it just the way it works.

     

     

    Monday, October 08, 2007 4:34 PM
  • Did you guys figure out the last and correct answer? Does or doesn't occur ANY memory leak if not calling EndInvoke on async delegates?
    Wednesday, October 28, 2009 12:31 AM
  • Yes!  Check out Herb Sutter's recent article on async programming.
    http://www.ddj.com/go-parallel/article/showArticle.jhtml?articleID=222301165&pgno=2

    "For example, if you end up deciding you don't need the result after all because your work is being cancelled or you called the function speculatively or for any other reason, do you have to call the EndXxx method? Typically, yes (and on .NET specifically, always yes), because the BeginXxx call will allocate resources to launch and track the work, and the EndXxx call releases those resources. Hence, the .NET Design Guidelines document says: "Do ensure that the EndMethodName method is always called even if the operation is known to have already completed. This allows exceptions to be received and any resources used in the async operation to be freed." This is a common source of errors as even expert programmers, and books written by true gurus, have got this wrong by thinking the EndXxx call was optional, or just forgetting to write it on all paths. "

    I think that's pretty clear : )
    Monday, January 18, 2010 5:09 AM
  • But don't those classes have finalizers to free allocated resources in case someone forgot?
    Sunday, May 09, 2010 3:17 AM