Results 1 to 3 of 3

Thread: Proper Cleanup

  1. #1

    Thread Starter
    Fanatic Member clarkgriswald's Avatar
    Join Date
    Feb 2000
    Location
    USA
    Posts
    769

    Proper Cleanup

    I have a class (class A) that is instantiated by a consumer. Class A instantiates class B privately. Class B creates a Timers.Timer. Originally, in class B's Finalize event, I disposed of the timer and all was fine. However, class A is COM visible and when consumed by a COM application, when class A is set to Nothing, the Finalize events never fire for class B and thus my timer is still running (not properly cleaned up). Should I implement IDisposable on class A and class B and rely on the user to call .Dispose on class A, which would then call .Dispose on class B, which calls the timer's dispose method? Is that overkill? Any suggestions would be appreciated.

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Proper Cleanup

    Take it from me: this is a bad design and it's impossible to safely dispose of resources as-is.

    I'm going to call A "outer" and B "inner". Here's the problem.

    Finalizers happen in a non-deterministic order. So it's possible, in a finalization context, the GC could decide to destroy B before A. If it does this, then A cannot legally do anything with B. That is why we write the Dispose() pattern with finalizer like this:
    Code:
    Public Sub Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
    
    Sub Finalize()
        Dispose(False)
    End Sub
    
    Protected Overrides Sub Dispose(ByVal isSafe As Boolean)
        ' Deal with unmanaged objects like handles.
    
        If isSafe Then
            ' Deal with .NET objects.
        End If
    End Sub
    You can only safely deal with .NET objects if you know Dispose() has been called. So this pattern protects those things and ensures you only get rid of unmanaged things in a finalizer.

    Note this means it's not safe for B to dispose its Timer from a finalizer, either. While that Timer has an unmanaged resource, it is itself a .NET object so if you're in a finalizer context you have to hope its finalizer does its work.

    You should strive to make it such that when the COM code is done with your .NET object, YOU call Dispose. I'm assuming the COM object has a Release(), that's probably the best place. You have to figure out if there are no references left, then call Dispose() if that's the case. This gives you a deterministic disposal order. (Or: perhaps COM could call Dispose() before releasing the object.)

    If the Finalizers aren't running, maybe the COM code creates that object? Maybe in that context, the GC doesn't run or doesn't call finalizers. If that's the case, well, the last paragraph still holds true.

    So yes. One of the most basic rules of IDisposable is it is viral:
    If you reference an IDisposable, YOU should be IDisposable.
    Since B holds a Timer, B needs to be IDisposable. Since A holds a B, A needs to be IDisposable. And so on.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  3. #3

    Thread Starter
    Fanatic Member clarkgriswald's Avatar
    Join Date
    Feb 2000
    Location
    USA
    Posts
    769

    Re: Proper Cleanup

    I think I got what you are saying, so for class A (outer) I have:

    Code:
    Private disposedValue As Boolean
    
        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not disposedValue Then
                If disposing Then
                    classB.Dispose()
                End If
            End If
            disposedValue = True
        End Sub
    
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
    
        Protected Overrides Sub Finalize()
            Dispose(False)
            MyBase.Finalize()
        End Sub
    In class B (inner) I have:

    Code:
    Private disposedValue As Boolean
    
        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not disposedValue Then
                If disposing Then
                    TimerX.Dispose()
                End If
            End If
            disposedValue = True
        End Sub
    
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
        End Sub
    A follow up question, I control the COM application that consumes class A, so I can physically call classA.Dispose() - is that what you meant? Not sure how to ensure dispose is called any other way.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width