Garbage collection and async operations

B

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?
 
J

Jon Shemitz

Bob said:
' 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.
 
B

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)?
 
J

Jon Shemitz

Bob said:
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.
 
B

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
 
J

Jon Shemitz

Bob said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top