I apologize in advance; this is a long question. I've tried to simplify as much as I can but it's still a bit more long-winded than I'd care to see.
In some legacy code, we've got a VB6 collection. This collection adds objects via the .Add method and removes them via the .Remove method. However, via tracing I can see that sometimes when the .Remove is called it appears that the class terminate for the object isn't called. But it's not consistent; it happens only rarely and I can't isolate the circumstances under which it fails to fire the class terminate.
Consider the following demonstration code:
Option Explicit
Private Const maxServants As Integer = 15
Private Const className As String = "Master"
Private Sub Class_Initialize()
Debug.Print className & " class constructor "
Set g_coll1 = New Collection
Dim i As Integer
For i = 1 To maxServants
Dim m_servant As Servant
Set m_servant = New Servant
m_servant.InstanceNo = i
g_coll1.Add Item:=m_servant, Key:=CStr(i)
Debug.Print "Adding servant " & m_servant.InstanceNo
Next
End Sub
Private Sub Class_Terminate()
Dim i As Integer
For i = maxServants To 1 Step -1
g_coll1.Remove (CStr(i))
Next
Debug.Print className & " class terminator "
Set g_coll1 = Nothing
Exit Sub
End Sub
and
Option Explicit
Private Const className As String = "Servant"
Private m_instanceNo As Integer
Private Sub Class_Initialize()
m_instanceNo = 0
Debug.Print className & " class constructor "
End Sub
Public Property Get InstanceNo() As Integer
InstanceNo = m_instanceNo
End Property
Public Property Let InstanceNo(newInstanceNo As Integer)
m_instanceNo = newInstanceNo
End Property
Private Sub Class_Terminate()
Debug.Print className & " class terminator for " & CStr(Me.InstanceNo)
End Sub
and this is the test harness code:
Option Explicit
Global g_coll1 As Collection
Public Sub Main()
Dim a As Master
Set a = New Master
End Sub
Now, for every run, the class_terminate of Servant is always invoked. And I can't see anything in the production code which should keep the object in the collection referenced.
1.) Is there any way to force the class terminate on the Remove? That is, can I call Obj.Class_Terminate and be assured it will work every time?
2.) On my production code (and my little test app) the classes are marked "Instancing - 5 MultiUse". I realize this may be some sort of threading issue; is there an effective way to prove (or disprove) that multi-threading is the cause of this issue--some sort of tracing I might add or some other sort of test I might perform?
EDIT: Per MarkJ's insightful comment below, I should add that the test posted above and the production code are both ActiveX exe's--part of the reason I ask about mulit-threading.
We had a similar issue, but where we could trace the non-termination of the objects to be down to an instance being held elsewhere in our application.
In the end, we had to write our Termination method like this:
So whenever you really wanted the class to be terminated (i.e. when you call g_coll1.Remove), you can also call
Terminate
on the held object.I think that you could also make Class_Terminate public, but that's a bit ugly in my opinion.
Re your point (2), I think it's very unlikely to be a threading issue, but I can't think of a good proof/test off the top of my head. I suppose one very quite thing you can consider is: do you manually use threading in your application? VB6 doesn't do much threading automatically... (see edit below)
[Edit] MarkJ tells us that apparently building as an ActiveX application means that VB6 does automatically do threading. Someone else will have to explore the implications of this, since I wasn't familiar with it!