<$BlogRSDUrl$>

Friday, January 09, 2004

Memory leak with fire and forget on delegate  

Asynchronous programming has become an increasingly important part of my applications. While Microsoft suggests using delegates, here is an important consideration. Delegate.BeginInvoke() and memory leaks...
I need to make a retraction. It's just come to my attention that the runtime guys are reserving the right to leak stuff if you never call EndInvoke. In the course of trying to pin down who was saying what on this topic, I ran across this statement in the .net sdk docs for the 1.1 framework: "CAUTION Always call EndInvoke after your asynchronous call completes." As far as I can tell, this is the first time they've come right out and said this. And it's contrary to what guys like Box and Grimes have published in books and articles on this topic [2,3]. So if you want fire-and-forget semantics, you'll have to roll it yourself. An example of one possible approach appears below my sig as note [4]. Apparantly this is still being discussed internally, so there's a chance they might retract the cautionary note they've introduced in the 1.1 docs; but that's probably unlikely at this point. -Mike DevelopMentor http://staff.develop.com/woodring [1] ms-help://MS.NETFrameworkSDKv1.1/cpguidenf/html/cpovrasynchronousprogram mingoverview.htm [2] "Essential .NET", 1st Ed., by Don Box, page 194. [3] ms-help://MS.MSDNQTR.2003JAN.1033/dnmag01/html/ASync0108.htm [4]
See also:Supporting the Fire-and-Forget Idiom for Asynchronous Delegates Without Leaking Here is my attempt at traslating this into VB (this translation is much harder than usual)
' AsyncHelper
'
' This class provides a FireAndForget method that facilitates
' calling an arbitrary method asynchronously w/o worrying about
' or waiting for the return value.
'
' The usage model is along these lines (example assumes
' the existence of  Sub SomeMethod(string, double).
'
''Then you create a delegate with the same signature
' Delegate Sub SomeDelegate(ByVal string1 As String, ByVal integer1 as Integer)
'
''Then pass the address of the background method and its arguments
' Dim d As SomeDelegate = AddressOf SomeMethod
' AsyncHelper.FireAndForget(d, New Object() {string1, integer1})

'
' Note - there's an extra (IMO) level of indirection in the
' following code that I don't think should be necessary.  The
' goal is to call Delegate.DynamicInvoke using BeginInvoke.
' Using BeginInvoke gets us asynchrony, using DynamicInvoke
' makes this generically reusable for any delegate.  However,
' when I call DynamicInvoke directly I get an undetailed
' execution engine fatal error.  When I use BeginInvoke to
' call a shim method that just turns around and calls
' DynamicInvoke, things work.  Strange (and consistent on
' the 1.0 and 1.1 runtimes).
'
Class AsyncHelper
    Delegate Sub DynamicInvokeShimProc(ByVal d As [Delegate], ByVal args As Object())
    '
    Private Shared dynamicInvokeShim As DynamicInvokeShimProc = New DynamicInvokeShimProc(AddressOf DynamInvokeShim)
    Private Shared dynamicInvokeDone As AsyncCallback = New AsyncCallback(AddressOf DynamInvokeDone)

    Public Shared Sub FireAndForget(ByVal d As [Delegate], ByVal args As Object())
        dynamicInvokeShim.BeginInvoke(d, args, dynamicInvokeDone, Nothing)
    End Sub

    Private Shared Sub DynamInvokeShim(ByVal d As [Delegate], ByVal args As Object())
        d.DynamicInvoke(args)
    End Sub

    Private Shared Sub DynamInvokeDone(ByVal ar As IAsyncResult)
        dynamicInvokeShim.EndInvoke(ar)
    End Sub

End Class

Comments: Post a Comment

This page is powered by Blogger. Isn't yours?