Page 2 of 3
Resize pictures
Posted: Thu Jan 14, 2021 7:13 pm
by Chris
^^^ It will be automatically destroyed, when the Garbage Collector decides to kick in, to be precise, and this could potentially take a lot of time. Manually destroying objects that are not longer needed releases the resources they use right now and frees memory more quickly.
Resize pictures
Posted: Thu Jan 14, 2021 7:23 pm
by FFF
Agreed. But, OTOH, memory in itself isn't a value, as long as it is not in use - the GC will and has to kick in, if there's some threshold crossed.
Resize pictures
Posted: Fri Jan 15, 2021 10:21 am
by Chris
It could be unmanaged resources though, which the GC has no idea that they are related to the managed objects. A very small and simple .Net object could have opened a thousand unmanaged file handles, allocate large chunks of unmanaged memory with Win32 API that the GC has no idea about. Especially in a tight loop, as in the sample below, you can end up allocating too many resources, bringing the PC to a crawl, before the GC suddenly kicks in and tries to release everything (collect all objects created) in one go:
Code: Select all
FUNCTION Start() AS VOID
LOCAL oObject AS SimpleClass
FOR LOCAL n := 1 AS INT UPTO 1000
oObject := SimpleClass{n}
oObject: DoSomeWorkWithIt()
// all of the above 1000 objects are still alive in memory, until the GC kills them all together
NEXT
CLASS SimpleClass IMPLEMENTS IDisposable
CONSTRUCTOR(nID AS INT)
SELF:AllocateHugeAmountOfResources()
DESTRUCTOR()
SELF: Dispose()
METHOD Dispose() AS VOID
SELF:FreeHugeAmountOfResources()
END CLASS
But if you added a oObject: Dispose() at the end of each iteration, resources would be released exactly at the time they are not needed anymore and memory consumption would always remain at the minimum possible.
Even worse, such an object could still be referenced accidentally while is not needed anymore, thus making it impossible to collect it:
Code: Select all
FUNCTION Start() AS VOID
LOCAL oObject1,oObject2 AS SimpleClass
oObject1 := SimpleClass{1}
oObject2 := SimpleClass{2}
oObject1:DoSomeWorkWithIt()
oObject2:DoSomeWorkWithIt()
//
// <a lot of other code in here, having nothing to do with those objects>
//
// this line causes those two objects and their resources
// to still be kept alive till here by the Garbage Collector
? oObject1:GetResult() + oObject2:GetResult()
But, by using BEGIN USING,instead you can limit the visibility of the variables to only where they are really needed, force yourself to write more efficient code and make resource management much more efficient:
Code: Select all
FUNCTION Start() AS VOID
LOCAL nResult AS INT
BEGIN USING VAR oObject1 := SimpleClass{1}
oObject1:DoSomeWorkWithIt()
nResult := oObject1:GetResult()
END USING
// The resources used by oObject1 have already been freed here, because
// Dispose() was automatically called from the BEGIN...END USING statement
// Also oObject1 is not visible here anymore, so it can not be used accidentally
// and the GC can also completely collect it
BEGIN USING VAR oObject2 := SimpleClass{2}
oObject2:DoSomeWorkWithIt()
nResult += oObject2:GetResult()
END USING
// same for oObject2, it can be collected now
// <a lot of other code in here, having nothing to do with those objects>
When BEGIN USING is used this way, it both limits the scope of the variable and also ensures Dispose() is automatically called, leading to the more "clean" results.
Btw, I am a bit bothered you cannot use my favorite "LOCAL" syntax for BEGIN USING above, will log this.
Resize pictures
Posted: Sat Jan 16, 2021 7:52 am
by Horst
Hi Chris
To be sure.
So this Code is not right ? BTW it works (the Local)
LOCAL oGraphics AS Graphics
BEGIN USING oGraphics := Graphics.FromImage(oNewImage)
oGraphics:DrawImage(oImage, 0, 0, newWidth, newHeight)
END USING
I have to change it to Begin Using VAR oGraphics and removing the LOCAL Statement ?
And:
// PersonenIndex erstellen
cFilename := SELF:gdbPerson
odb := _cryptserver {WorkDir () + SELF:cPathRoot+cFilename,TRUE,FALSE,"DBFCDX"}
// 1
lOrderOk := odb:createorder ( "Id_Person",,"Id_Person" ,{|| _Field->Id_Person} , FALSE,FALSE)
IF ! lOrderOk ; SELF:WriteCgiLog ("Person, Id_Person Index ERROR !") ; ENDIF
-------> Hier i have to insert the ODB:DISPOSE () ?? <-------------------------
// Personen Zusatz Index erstellen
cFilename := SELF:gdbpZusatz
odb := _cryptserver {WorkDir () + SELF:cPathRoot+cFilename,TRUE,FALSE,"DBFCDX"}
// 1
lOrderOk := odb:createorder ( "Id_Person",,"Id_Person+Nachname" ,{|| _Field->Id_Person+_Field->Nachname} , FALSE,FALSE)
IF ! lOrderOk ; SELF:WriteCgiLog ("pZusatz, Id_Person Index ERROR !") ; ENDIF
Horst
Resize pictures
Posted: Sat Jan 16, 2021 9:24 am
by Chris
Hi Horst,
The code with LOCAL and BEGIN USING is still right, only "issue" is that you can still (accidentally) use the oGraphics var even after the END USING line, where the object has been disposed, so does not make sense reusing it (except if you instantiate it again). If you use the VAR syntax, the variable wil be visible only inside the BEGIN...END construct, attempting to use it below will lead to a compiler error, thus preventing possible programmer mistakes.
About using Dispose() in your second sample, it's just good programming practice. In this specific sample, I doubt it will make a noticeable difference, it's just considered a good habbit disposing objects that you no longer needed. If you are used to doing it even in places where it is not really important, you will be doing it also in places where it can make a big difference.
Resize pictures
Posted: Tue Jan 19, 2021 7:02 am
by Horst
Hi Chris
But it looks like DbServer has no Dispose() Methode, cant compile.
Horst
Resize pictures
Posted: Tue Jan 19, 2021 8:07 am
by Chris
Hi Horst,
Not every class has a Dispose() method, it's only those that do allocate external resources and it makes sense to free them ASAP that have it. DBServer's only external resource it uses is to open a dbf file, and there's already a method to dispose that resource, it's just called Close() instead of Dispose() and of course you are most probably calling it already.
The DBServer class was designed more than 25 years ago, when we did not have IDispose interfaces etc, but Close() is indeed working very similarly to Dispose(), in the sense you can also omit calling it, and just let the object go out of scope, wait until the GC collects it, which then calls it Destructor() (Axit() in VO), which in turn calls Close() in order to close the assosiated file. It would just take more time to close the file this way, until there are zero references to this DBServer object anymore and the GC kicks in.
Maybe we should actually now introduce a Dispose() method in DBServer class, which would simply call Close(), to make it more modern and allow DBServer to be used with BEGIN...END USING.
Resize pictures
Posted: Tue Jan 19, 2021 8:50 am
by Horst
Hi Chris
I think the Dispose for dbServer is not needed, only a comment in the help file that Close will call Dispose, Close is more readable.
Horst
Resize pictures
Posted: Tue Jan 19, 2021 9:44 am
by wriedmann
Hi Chris,
adding a Dispose() method in the DBServer class makes definitively sense because DBServers can be used also in non-legacy code.
I had tried to build a CoreDBFServer class that does not uses DBServer internally, but have failed.
Maybe we can start that together: a DBFServer class that is usable in non VO code.
Wolfgang
Resize pictures
Posted: Tue Jan 19, 2021 11:24 am
by Chris
Horst: Dispose() and destructor are different things. Dispose() is just a name, by convention classes in .Net that need to release resources implement a method with that name, but it could be any other name really. There is no Dispose() method at all in DBServer, there is only Close(), which is called either by the programmer directly, or by the garbage collector when it collects (destructs) the object (Close() is called from the destructor()).
Wolfgang: What do you mean you failed? Do you mean it was too much work? You could maybe just use the original code of DBServer and adjust it to not use USAULs or other VO types (step by step of course, as it is a lot of code).