Home All Groups Group Topic Archive Search About

Garbage collection and async operations

Author
30 Nov 2006 6:49 PM
Bob Altman
Suppose I have the following code:

  ' Get a new instance of some class
  Dim myWorker As New MyWorker

  ' Start a long-running operation on a background thread.
  dim deleg as new SomeMethodDeleg(AddressOf myWorker.SomeMethod)
  deleg.BeginInvoke(...)

  ' Abandon the object instance
  myWorker = Nothing

At this point I would expect my MyWorker object to be eligible for garbage
collection.  But it's still busy running code on a background thread.  What
happens when the GC runs?

Author
30 Nov 2006 8:04 PM
Jon Shemitz
Bob Altman wrote:

>   ' Get a new instance of some class
>   Dim myWorker As New MyWorker
>
>   ' Start a long-running operation on a background thread.
>   dim deleg as new SomeMethodDeleg(AddressOf myWorker.SomeMethod)
>   deleg.BeginInvoke(...)
>
>   ' Abandon the object instance
>   myWorker = Nothing
>
> At this point I would expect my MyWorker object to be eligible for garbage
> collection.  But it's still busy running code on a background thread.  What
> happens when the GC runs?

Your expectation is wrong. The ThreadPool thread has a reference to
your delegate (it has to, to Invoke it) and so the delegate will
survive until you call EndInvoke.

--

..NET 2.0 for Delphi Programmers
www.midnightbeach.com/.net
What you need to know.
Author
30 Nov 2006 8:48 PM
Bob Altman
I'm not sure I understand the ramifications of your answer.  Let me change
the question slightly and see what shakes out.  Suppose I have a class that
provides its own BeginXxx/EndXxx implementation:

Class Test
  Public Sub DoWork()
    ' <Perform some long-running operation>
  End Sub

  Private _deleg As New TestDelegate(AddressOf DoWork)

  Public Function BeginDoWork( _
  ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
    Return _deleg.BeginInvoke(callback, state)
  End Function

  Public Sub EndDoWork(asyncResult As IAsyncResult)
    _deleg.EndInvoke(asyncResult)
  End Sub
End Class

Now, my main program does the following:

  Dim test As New Test
  test.BeginDoWork(Nothing, Nothing)
  test = Nothing

Does something keep the (now abandoned) Test instance alive forever (since
I'm never calling the delegate's EndInvoke)?
Author
30 Nov 2006 9:27 PM
William Sullivan
Yep.  Calling BeginInvoke but not calling EndInvoke causes a memory leak.  To
put it simply, the CLR is waiting for you to call EndInvoke (to get any
results from the operation or to deal with any exceptions thrown by the
operation) and will therefore not collect the thread object.

Show quote
"Bob Altman" wrote:

> I'm not sure I understand the ramifications of your answer.  Let me change
> the question slightly and see what shakes out.  Suppose I have a class that
> provides its own BeginXxx/EndXxx implementation:
>
> Class Test
>   Public Sub DoWork()
>     ' <Perform some long-running operation>
>   End Sub
>
>   Private _deleg As New TestDelegate(AddressOf DoWork)
>
>   Public Function BeginDoWork( _
>   ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
>     Return _deleg.BeginInvoke(callback, state)
>   End Function
>
>   Public Sub EndDoWork(asyncResult As IAsyncResult)
>     _deleg.EndInvoke(asyncResult)
>   End Sub
> End Class
>
> Now, my main program does the following:
>
>   Dim test As New Test
>   test.BeginDoWork(Nothing, Nothing)
>   test = Nothing
>
> Does something keep the (now abandoned) Test instance alive forever (since
> I'm never calling the delegate's EndInvoke)?
>
>
>
Author
30 Nov 2006 10:06 PM
Bob Altman
So, let me make sure I have this all correct:

1. If I call a delegate's BeginInvoke method then the invoked routine will
run to compltion on a thread pool thread, regardless of whether or not that
routine or its delegate goes out of scope.

2. When the background routine completes, its thread pool thread may or may
not be assigned some other work, or that thread may be terminated.  This
mechanism is independent of whether or not I call EndInvoke.

3. If the Delegate goes out of scope before I call EndInvoke then I leak the
Delegate (which presumably holds the return value from the invoked routine
and the Exception object thrown by the invoked routine).  Specifically, some
magic in the CLR keeps the Delegate alive until its EndInvoke method is
called.

On a tangentially related subject:  Is there a memory leak if I call a
Control object's BeginInvoke method (to run a routine on the UI thread) and
never call the Control's EndInvoke method?  Specifically, I use this code
pattern for handling events that may be raised on another thread:

Sub MyEventHandler(sender as Object, e as EventArgs)
  If Me.InvokeRequired() Then
    ' Queue the call to the UI thread
    Dim deleg As New EventHandler(AddressOf MyEventHandler)
    Dim args() as Object = {sender, e}
    Me.BeginInvoke(deleg, args)
  Else
    ' This code runs on the UI thread
  End If
End Sub

Show quote
"William Sullivan" <WilliamSulli***@discussions.microsoft.com> wrote in
message news:A7B526D0-81A3-4333-9106-4B37D1A063E3@microsoft.com...
> Yep.  Calling BeginInvoke but not calling EndInvoke causes a memory leak.
> To
> put it simply, the CLR is waiting for you to call EndInvoke (to get any
> results from the operation or to deal with any exceptions thrown by the
> operation) and will therefore not collect the thread object.
>
> "Bob Altman" wrote:
>
>> I'm not sure I understand the ramifications of your answer.  Let me
>> change
>> the question slightly and see what shakes out.  Suppose I have a class
>> that
>> provides its own BeginXxx/EndXxx implementation:
>>
>> Class Test
>>   Public Sub DoWork()
>>     ' <Perform some long-running operation>
>>   End Sub
>>
>>   Private _deleg As New TestDelegate(AddressOf DoWork)
>>
>>   Public Function BeginDoWork( _
>>   ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
>>     Return _deleg.BeginInvoke(callback, state)
>>   End Function
>>
>>   Public Sub EndDoWork(asyncResult As IAsyncResult)
>>     _deleg.EndInvoke(asyncResult)
>>   End Sub
>> End Class
>>
>> Now, my main program does the following:
>>
>>   Dim test As New Test
>>   test.BeginDoWork(Nothing, Nothing)
>>   test = Nothing
>>
>> Does something keep the (now abandoned) Test instance alive forever
>> (since
>> I'm never calling the delegate's EndInvoke)?
>>
>>
>>
Author
30 Nov 2006 10:20 PM
Jon Shemitz
Bob Altman wrote:

> 1. If I call a delegate's BeginInvoke method then the invoked routine will
> run to compltion on a thread pool thread, regardless of whether or not that
> routine or its delegate goes out of scope.

Yes.

> 2. When the background routine completes, its thread pool thread may or may
> not be assigned some other work, or that thread may be terminated.  This
> mechanism is independent of whether or not I call EndInvoke.

Yes.

> 3. If the Delegate goes out of scope before I call EndInvoke then I leak the
> Delegate (which presumably holds the return value from the invoked routine
> and the Exception object thrown by the invoked routine).  Specifically, some
> magic in the CLR keeps the Delegate alive until its EndInvoke method is
> called.

No. The return value or exception are stored in the object that
implements IAsyncResult. (Think about it: Many (most?) delegates are
never executed asynchronously. The IAsyncResult is explicitly created
by the runtime when you call BeginInvoke. The IAsyncResult must be
passed to EndInvoke. Where would *you* put the result?)

> On a tangentially related subject:  Is there a memory leak if I call a
> Control object's BeginInvoke method (to run a routine on the UI thread) and
> never call the Control's EndInvoke method?  Specifically, I use this code
> pattern for handling events that may be raised on another thread:

No. This is explicitly documented as safe.

--

..NET 2.0 for Delphi Programmers
www.midnightbeach.com/.net
What you need to know.
Author
30 Nov 2006 11:25 PM
Bob Altman
So if I really don't care about the result of the asynchronous operation
(including any exception that it may have thrown) then nothing bad happens
if I don't call EndInvoke.  (My actual application has a Cancel method on
the class containing the long-running async operation.  But cancellation may
take a while.  So after I call Cancel I just get rid of my reference to the
object and assume that it will eventually stop running and the GC will
eventually collect it.)

Thanks a million for helping me understand this!

  - Bob

Show quote
"Jon Shemitz" <j**@midnightbeach.com> wrote in message
news:456F593E.ADF5837E@midnightbeach.com...
> Bob Altman wrote:
>
>> 1. If I call a delegate's BeginInvoke method then the invoked routine
>> will
>> run to compltion on a thread pool thread, regardless of whether or not
>> that
>> routine or its delegate goes out of scope.
>
> Yes.
>
>> 2. When the background routine completes, its thread pool thread may or
>> may
>> not be assigned some other work, or that thread may be terminated.  This
>> mechanism is independent of whether or not I call EndInvoke.
>
> Yes.
>
>> 3. If the Delegate goes out of scope before I call EndInvoke then I leak
>> the
>> Delegate (which presumably holds the return value from the invoked
>> routine
>> and the Exception object thrown by the invoked routine).  Specifically,
>> some
>> magic in the CLR keeps the Delegate alive until its EndInvoke method is
>> called.
>
> No. The return value or exception are stored in the object that
> implements IAsyncResult. (Think about it: Many (most?) delegates are
> never executed asynchronously. The IAsyncResult is explicitly created
> by the runtime when you call BeginInvoke. The IAsyncResult must be
> passed to EndInvoke. Where would *you* put the result?)
>
>> On a tangentially related subject:  Is there a memory leak if I call a
>> Control object's BeginInvoke method (to run a routine on the UI thread)
>> and
>> never call the Control's EndInvoke method?  Specifically, I use this code
>> pattern for handling events that may be raised on another thread:
>
> No. This is explicitly documented as safe.
>
> --
>
> .NET 2.0 for Delphi Programmers
> www.midnightbeach.com/.net
> What you need to know.
Author
1 Dec 2006 1:40 PM
William Sullivan
Just to be clear, you MUST call EndXXX after calling BeginXXX to prevent
memory leaks EXCEPT Control.BeginInvoke. 

For example, if you call SqlCommand.BeginExecuteNonQuery (a query that
doesn't return anything), you might think you can just ignore calling
SqlCommand.EndExecuteNonQuery because you may not care if it succeeds or not.
However, if you do not, you will cause a memory leak (and possibly some
resource leaks as well).
If you're in a worker thread and need to display some text, you can call
TextBox.BeginInvoke in order to pass the string you need to display across
threads to the UI thread in order to display it.  You will not need to call
TextBox.EndInvoke as BeginInvoke in Controls will not leak memory or
resources if EndInvoke is not called.

Show quote
"Bob Altman" wrote:

> So if I really don't care about the result of the asynchronous operation
> (including any exception that it may have thrown) then nothing bad happens
> if I don't call EndInvoke.  (My actual application has a Cancel method on
> the class containing the long-running async operation.  But cancellation may
> take a while.  So after I call Cancel I just get rid of my reference to the
> object and assume that it will eventually stop running and the GC will
> eventually collect it.)
>
> Thanks a million for helping me understand this!
>
>   - Bob
>
> "Jon Shemitz" <j**@midnightbeach.com> wrote in message
> news:456F593E.ADF5837E@midnightbeach.com...
> > Bob Altman wrote:
> >
> >> 1. If I call a delegate's BeginInvoke method then the invoked routine
> >> will
> >> run to compltion on a thread pool thread, regardless of whether or not
> >> that
> >> routine or its delegate goes out of scope.
> >
> > Yes.
> >
> >> 2. When the background routine completes, its thread pool thread may or
> >> may
> >> not be assigned some other work, or that thread may be terminated.  This
> >> mechanism is independent of whether or not I call EndInvoke.
> >
> > Yes.
> >
> >> 3. If the Delegate goes out of scope before I call EndInvoke then I leak
> >> the
> >> Delegate (which presumably holds the return value from the invoked
> >> routine
> >> and the Exception object thrown by the invoked routine).  Specifically,
> >> some
> >> magic in the CLR keeps the Delegate alive until its EndInvoke method is
> >> called.
> >
> > No. The return value or exception are stored in the object that
> > implements IAsyncResult. (Think about it: Many (most?) delegates are
> > never executed asynchronously. The IAsyncResult is explicitly created
> > by the runtime when you call BeginInvoke. The IAsyncResult must be
> > passed to EndInvoke. Where would *you* put the result?)
> >
> >> On a tangentially related subject:  Is there a memory leak if I call a
> >> Control object's BeginInvoke method (to run a routine on the UI thread)
> >> and
> >> never call the Control's EndInvoke method?  Specifically, I use this code
> >> pattern for handling events that may be raised on another thread:
> >
> > No. This is explicitly documented as safe.
> >
> > --
> >
> > .NET 2.0 for Delphi Programmers
> > www.midnightbeach.com/.net
> > What you need to know.
>
>
>
Author
30 Nov 2006 9:46 PM
Jon Shemitz
Bob Altman wrote:

> Does something keep the (now abandoned) Test instance alive forever (since
> I'm never calling the delegate's EndInvoke)?

Actually, I was in a bit of a rush to get out the door to an
appointment, and I misspoke, slightly.

The ThreadPool thread that's executing your delegate obviously has to
have a reference to the delegate that it's executing, so clearly the
delegate can't be collected until it returns and the ThreadPool thread
is returned to the pool. (Presumably waiting threads have their
delegate-to-execute field cleared, precisely so that the delegates can
be collected.)

You should always call EndInvoke when you execute a delegate
asynchronously. The documentation is very firm about that, even if
it's not clear precisely why. Fwiw, I believe William Sullivan is
wrong about not calling EndInvoke causing a memory leak: the thread is
presumably returned to the pool as soon as the delegate returns, and
if you simply ignore the IAsyncResult that BeginInvoke returns, it
will ultimately be finalized and collected.

Take away messages:

1) Abandoning your MyWorker delegate will not cause premature
collection.

2) Not calling EndInvoke will not force the delegate to be immortal.

3) You should always call EndInvoke when you BeginInvoke a delegate.
If you don't want to bother, use ThreadPool.QueueUserWorkItem instead
of asynch execution.

--

..NET 2.0 for Delphi Programmers
www.midnightbeach.com/.net
What you need to know.
Author
1 Dec 2006 1:46 PM
William Sullivan
CLR Via C# by Jeffrey Richter, MS Press, page 615:

"You must call EndXXX or you will leak resources...   [T]he CLR allocates
some internal resources when you initiate an asynchronous operation.  When
the operation completes, the CLR will hold onto these resources until EndXXX
is called.  If EndXXX is never called, these resources remain allocated and
will be reclaimed only when the process terminates."

Awesome book.  Every C# developer should read it.

Show quote
"Jon Shemitz" wrote:

> Bob Altman wrote:
>
> > Does something keep the (now abandoned) Test instance alive forever (since
> > I'm never calling the delegate's EndInvoke)?
>
> Actually, I was in a bit of a rush to get out the door to an
> appointment, and I misspoke, slightly.
>
> The ThreadPool thread that's executing your delegate obviously has to
> have a reference to the delegate that it's executing, so clearly the
> delegate can't be collected until it returns and the ThreadPool thread
> is returned to the pool. (Presumably waiting threads have their
> delegate-to-execute field cleared, precisely so that the delegates can
> be collected.)
>
> You should always call EndInvoke when you execute a delegate
> asynchronously. The documentation is very firm about that, even if
> it's not clear precisely why. Fwiw, I believe William Sullivan is
> wrong about not calling EndInvoke causing a memory leak: the thread is
> presumably returned to the pool as soon as the delegate returns, and
> if you simply ignore the IAsyncResult that BeginInvoke returns, it
> will ultimately be finalized and collected.
>
> Take away messages:
>
> 1) Abandoning your MyWorker delegate will not cause premature
> collection.
>
> 2) Not calling EndInvoke will not force the delegate to be immortal.
>
> 3) You should always call EndInvoke when you BeginInvoke a delegate.
> If you don't want to bother, use ThreadPool.QueueUserWorkItem instead
> of asynch execution.
>
> --
>
> ..NET 2.0 for Delphi Programmers
> www.midnightbeach.com/.net
> What you need to know.
>
Author
1 Dec 2006 8:16 PM
Jon Shemitz
William Sullivan wrote:
>
> CLR Via C# by Jeffrey Richter, MS Press, page 615:
>
> "You must call EndXXX or you will leak resources...   [T]he CLR allocates
> some internal resources when you initiate an asynchronous operation.  When
> the operation completes, the CLR will hold onto these resources until EndXXX
> is called.  If EndXXX is never called, these resources remain allocated and
> will be reclaimed only when the process terminates."

Interesting. I would expect any such resources to be stored in the
IAsyncResult, and I would also expect that the IAsyncResult would have
a finalizer that takes care of freeing its resources if EndInvoke is
not called.

This pp spurred me to do a test that I should have done years ago -
look to see if the IAsyncResult actually has a finalizer. The below
code shows that (at least in 2.0) it does not - that the DeclaringType
is System.Object.

So, my expectations were wrong and William was right about not calling
EndInvoke resulting in a memory leak.

Imho, there are two things that are awfully strange, here: First, why
doesn't the IAsyncResult have a finalizer? My first guess would be
that it's a sort of optimization, but it doesn't seem like it would
save all that many cycles; perhaps there are some internal reasons why
a System.Runtime class can't have a finalizer.

Second, why the 8&%$ can't the dox have said all along "not calling
EndInvoke will cause a resource leak, because the IAsyncResult doesn't
have a finalizer"? It would have saved so much wheel spinning. (Maybe
MS assumed we'd all be clever enough to just check for a finalizer the
moment we wondered why they told us to always call EndInvoke.)

//

using System;
using System.Reflection;
using System.Diagnostics;

namespace IAsyncResultFinalizer
{
  class Program
  {
    static void Main(string[] args)
    {
      Method F = Foo;
      IAsyncResult Async = F.BeginInvoke(null, null);

      Type TypeA = Async.GetType();
      MethodInfo Finalizer = TypeA.GetMethod("Finalize",
        BindingFlags.Instance|BindingFlags.NonPublic);
      Debug.Assert(Finalizer != null);

      Console.WriteLine("Finalizer.DeclaringType.FullName = {0}",
        Finalizer.DeclaringType.FullName);
      Console.WriteLine("Finalizer.ReflectedType.FullName = {0}",
        Finalizer.ReflectedType.FullName);

      F.EndInvoke(Async);
      Console.ReadLine();
    }

    static void Foo() { }
  }

  delegate void Method();
}

--

..NET 2.0 for Delphi Programmers
www.midnightbeach.com/.net
What you need to know.
Author
1 Dec 2006 8:48 PM
Jon Shemitz
Jon Shemitz wrote:

> This pp spurred me to do a test that I should have done years ago -
> look to see if the IAsyncResult actually has a finalizer. The below
> code shows that (at least in 2.0) it does not - that the DeclaringType
> is System.Object.

I must be getting old. I looked at my mail archives, and found that I
did a very similar test back in January of this year, when my tech
reviewer suggested that maybe I should mention why one must always
call EndInvoke. (In all fairness to myself, tech review is a rather
hectic process, and the whole period is pretty much a blur.)

At the time, he pointed out that the IASyncResult itself wouldn't
necessarily need to have a finalizer, any more than the typical class
that opens a file needs to have a finalizer: the AsyncWaitHandle would
have a finalizer, and that should suffice.

I concluded at the time that I didn't really know why you have to call
EndInvoke, and decided to just emphasize that we were told
emphatically to do so.

But, venturing into some rather speculative reverse engineering, it's
entirely possible that the
System.Runtime.Remoting.Messaging.AsyncResult doesn't normally create
a full-fledged WaitHandle, that it just creates an unmanaged wait
handle using raw Win32 calls, and only wraps that in a managed
WaitHandle when someone reads the IAsyncResult.AsyncWaitHandle
property. (Fwiw, the IAsyncResult.AsyncWaitHandle dox do sort of
suggest that this is the case.)

--

..NET 2.0 for Delphi Programmers
www.midnightbeach.com/.net
What you need to know.

AddThis Social Bookmark Button