|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Garbage collection and async operationsSuppose 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? Bob Altman wrote:
> ' Get a new instance of some class Your expectation is wrong. The ThreadPool thread has a reference to> 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 delegate (it has to, to Invoke it) and so the delegate will survive until you call EndInvoke. 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)? 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)? > > > 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)? >> >> >> Bob Altman wrote:
> 1. If I call a delegate's BeginInvoke method then the invoked routine will Yes.> 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 Yes.> 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 No. The return value or exception are stored in the object that> 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. 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 No. This is explicitly documented as safe.> 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: 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. 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. > > > Bob Altman wrote:
> Does something keep the (now abandoned) Test instance alive forever (since Actually, I was in a bit of a rush to get out the door to an> I'm never calling the delegate's EndInvoke)? 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. 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. > William Sullivan wrote:
> Interesting. I would expect any such resources to be stored in the> 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." 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(); } Jon Shemitz wrote:
> This pp spurred me to do a test that I should have done years ago - I must be getting old. I looked at my mail archives, and found that I> 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. 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.) |
|||||||||||||||||||||||