 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Ritchie Guest
|
Posted: Fri Jan 09, 2004 12:49 am Post subject: .NET reference-counting annoyances |
|
|
Knee-deep as I am in reference-counting, I'm a bit disappointed at
..NET's model for cleaning up objects, even though I know where they're
coming from.
..NET has three means to clean up objects, one of which is a
"convention" more than a means:
(1) If the object becomes unreachable, it goes away eventually, taking
other objects with it. Destruction code will NOT be called.
(2) The object implements IDisposable. If you call Dispose on the
object, it will run your override of Dispose.
Delphi uses this model for the destructor Destroy;, and X.Free will call
Dispose behind the scenes. This is a convention (you and your users
could just as well implement an IEatIt interface and call EatMe on it
, and recommended by Microsoft for speed reasons. Apart from that,
it's exactly like scenario # 1, above.
If you do not call .Free directly, then Destroy will *never* be called
(that may or may not be bad, depending on whether you're holding onto
limited/contentious resource sets).
(3) The object overrides Finalize; - this puts the object in a special
state where it gets collected as soon as it becomes unreachable.
Finalize will be called on it. There are technical reasons why this is
slower, including (IIRC) having to get the garbage collector on the job
right away (instead of whenever it cares to) and that Finalized objects
are in a two-pass system (once for finalizing, once for garbage
collection).
destructor Destroy; is not called in this scenario. You can call it
explicitly if you like, but you will have to be prepared to have it
called twice if you have any relevant cleanup code in the finalization
section of a unit.
---
It seems that if you want things properly 'reference-counted' (at least
for the purposes of going away), you must implement pattern # 3.
Borland's VCL-for-.NET certainly does a lot of it, especially for
database access, controls and graphics.
So for reference-counting/thread-safety, either implement pattern # 3,
or design the objects so that they have already cleaned up, or don't
care whether they're cleaned up.
As for the "weak reference pointer trick", for those who remember, where
you put the interface reference into a Pointer (instead of an IUnknown),
so that _AddRef and _Release don't get called...
TMyClass = class(TInterfacedObject,IBla)
private
FParent : Pointer;
protected
function GetParent: IBla;
procedure SetParent(const ABla: IBla);
public
// ...
property Parent: IBla read GetParent write SetParent;
end;
function TMyClass.GetParent: IBla;
begin
Result := IBla(FParent);
end;
procedure TMyClass.SetParent(const ABla: IBla);
begin
FParent := Pointer(ABla);
end;
Well, no pointers in .NET. For that matter, no _AddRef or _Release,
either :)
..NET *does* actually have something called a WeakReference. Seems simple
enough to operate:
TMyClass = class(TInterfacedObject,IBla)
private
FParent : WeakReference;
protected
function get_Parent: IBla;
procedure set_Parent(const ABla: IBla);
public
// ...
property Parent: IBla read get_Parent write set_Parent;
end;
function TMyClass.get_Parent: IBla;
var
TempObject : Object;
begin
TempObject := FParent.Target;
if TempObject=nil then
Result := nil
else
Result := IBla(TempObject);
end;
procedure TMyClass.set_Parent(const ABla: IBla);
begin
FParent := WeakReference.Create(ABla);
end;
A few points to make:
* The check in get_Parent is necessary, because "hard casts" aren't
actually hard any more - they are *all* checked, and a MyType(nil) "hard
cast" will actually fail.
* If you're wondering about the GetParent vs get_Parent oddity, just
know that if you call it GetParent (and there's a property that
references it), you'll get a hint to change it, and if you look in the
call stack from inside GetParent, you'll notice there's a get_Parent
stub that's been generated for you.
* Most annoyingly, WeakReference.Target will THROW AN EXCEPTION IF THE
OBJECT HAS BEEN COLLECTED. Instead of giving you nil. Where will this
happen? Most of the time, when the application is shutting down. Great.
* There's a WeakReference.IsAlive as well. IT THROWS THE EXCEPTION, TOO.
ACK!
I tracked down the WeakReference code from the Rotor project, and if
it's the same (it sure behaves the same), the exceptions are raised on
purpose.
So, courtesy of a little spelunking in the Rotor project, I give you a
new, very simple TWeakReference that doesn't complain :)
{$IFDEF CLR}
uses System.Runtime.InteropServices;
type
TWeakReference = class
private
FTargetHandle : GCHandle;
protected
function get_Target: TObject;
public
constructor Create(AObject: TObject);
property Target: TObject read get_Target;
end;
{$ENDIF}
{$IFDEF CLR}
{ TWeakReference }
constructor TWeakReference.Create(AObject: TObject);
begin
inherited Create;
FTargetHandle := GCHandle.Alloc(AObject,GCHandleType.Weak);
end;
function TWeakReference.get_Target: TObject;
var
IntHandle : IntPtr;
begin
IntHandle := IntPtr(FTargetHandle);
if IntPtr.Zero=IntHandle then
Result := nil
else
Result := FTargetHandle.Target;
end;
{$ENDIF}
There you go, my contribution to help your portability headaches :)
-- Ritchie Annand
Senior Software Architect
http://www.malibugroup.com
http://nimble.nimblebrain.net
http://wiki.nimblebrain.net
|
|
| Back to top |
|
 |
Nick Hodges (TeamB) Guest
|
|
| Back to top |
|
 |
Joanna Carter (TeamB) Guest
|
Posted: Fri Jan 09, 2004 9:02 am Post subject: Re: .NET reference-counting annoyances |
|
|
"Ritchie" <ritchieabatbat (AT) spamcop (DOT) netbatbat> a écrit dans le message de
news: [email]MPG.1a67a86ccb039f7d989694 (AT) forums (DOT) borland.com[/email]...
| Quote: | It seems that if you want things properly 'reference-counted' (at least
for the purposes of going away), you must implement pattern # 3.
Borland's VCL-for-.NET certainly does a lot of it, especially for
database access, controls and graphics.
|
But .NET does not support reference counting. It supports garbage collection
which is a whole different game. With reference counting the object is
returned to the heap when the last reference is lost; with garbage
collection it is only returned to the heap at some indeterminate time after
the last reference is lost.
<snipped interesting ideas on weak references>
I will peruse these and see what goodies I can extract :-)
Joanna
--
Joanna Carter (TeamB)
Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker
|
|
| Back to top |
|
 |
Marc Rohloff Guest
|
Posted: Fri Jan 09, 2004 4:40 pm Post subject: Re: .NET reference-counting annoyances |
|
|
Ritchie wrote on Thu, 8 Jan 2004 17:49:08 -0700 ...
| Quote: | (3) The object overrides Finalize
This is considered to be a very bad idea since finalization will force |
objects into 2nd phase garbage collection which may not happen in a
hurry.
| Quote: | destructor Destroy; is not called in this scenario.
explicitly if you like, but you will have to be prepared to have it
called twice if you have any relevant cleanup code in the finalization
section of a unit.
Correct but you should look at IDisposable implementation recommended by |
MS which would do this for you and handle the multiple destruction
caveat.
| Quote: | As for the "weak reference pointer trick", for those who remember, where
you put the interface reference into a Pointer (instead of an IUnknown),
so that _AddRef and _Release don't get called...
This is unneccessary. .NET has no need for a weak reference to solve |
this problem. A normal reference would work as well and not prevent GC
from collecting the object, even in self or circular referential cases.
The main reason to use a weak reference is if you want the object to be
able to be collected despite having a reference. For example in a cache.
Marc Rohloff
marc rohloff at bigfoot dot com
|
|
| Back to top |
|
 |
Ritchie Guest
|
Posted: Fri Jan 09, 2004 9:27 pm Post subject: Re: .NET reference-counting annoyances |
|
|
In article <3ffe0e67$1 (AT) newsgroups (DOT) borland.com>, [email]nickhodges (AT) yahoo (DOT) com[/email]
says...
Thank you, Nick :)
That covers a few topics I didn't hear about in the sessions :)
Overall, the porting effort is going pretty well. The
finalization/disposal issues threw more of a monkey wrench into some
things than I had expected. It took half a day to sort out program
shutdown issues (making the TWeakReference object really helped) - there
were a lot of "invalid handle" and "null reference" exceptions being
thrown on the way out.
Now I'm porting DUnit so I can run some unit tests ;)
Regards,
-- Ritchie Annand
Senior Software Architect
http://www.malibugroup.com
http://nimble.nimblebrain.net
http://wiki.nimblebrain.net
|
|
| Back to top |
|
 |
Ritchie Guest
|
Posted: Fri Jan 09, 2004 10:22 pm Post subject: Re: .NET reference-counting annoyances |
|
|
In article <MPG.1a6878cbb7c89284989d23 (AT) newsgroups (DOT) borland.com>,
[email]dont (AT) mailme (DOT) com[/email] says...
| Quote: | Ritchie wrote on Thu, 8 Jan 2004 17:49:08 -0700 ...
(3) The object overrides Finalize
This is considered to be a very bad idea since finalization will force
objects into 2nd phase garbage collection which may not happen in a
hurry.
|
Playing with Finalize practically, it seems that it *is* actually pretty
good about calling Finalize soon after. After the Finalize is called, it
does move the object into second phase, but the GC shouldn't be worrying
about collecting that too soon. I'd imagine most of the slowdown should
(must check MS's theory on this) come from the "oops, a finalizable
object has just had a reference let go - we must check its
unreachability NOW" instead of being able to lump many collectable
objects together at once in idle time.
System.GC.WaitForPendingFinalizers() can be called in an emergency :)
I'll have to read up on it a little more, because MS also goes into some
detail as to how you can "resurrect" objects (why?) and a number of
other things which I wonder why one would ever need :)
| Quote: | destructor Destroy; is not called in this scenario.
explicitly if you like, but you will have to be prepared to have it
called twice if you have any relevant cleanup code in the finalization
section of a unit.
Correct but you should look at IDisposable implementation recommended by
MS which would do this for you and handle the multiple destruction
caveat.
|
The thing is that destructor Destroy; in Delphi 8 *is* part of the
IDisposable implementation. Folks with code to migrate are going to have
to be prepared for Destroy to be called twice, which will mean a lot
more nil checks.
I'm going to take a 'boo through the page Nick posted, though, because
the recommended IDisposable implementation is:
procedure TX.Dispose;
begin
Dispose(True);
GC.SuppressFinalize(Self);
end;
// Listed in C# as the destructor - unlike Delphi's .Destroy, though,
// C#'s destructor is called by a Finalize
procedure TX.Finalize;
begin
Dispose(False);
end;
procedure TX.Dispose(Disposing: Boolean);
begin
if Disposing then
// clean up managed objects
// clean up unmanaged objects
inherited Dispose(Disposing);
end;
From the link Nick gave, here's a quote:
"We saw the special Delphi destructor pattern earlier, which is
translated into a silent implementation of IDisposable for you. It
should be made clear here that this pattern is not applicable when you
have a finalizer to implement. Borland's recommended coding style is to
not mix traditional destructors with CLR finalizers. If you need a
finalizer in your class, you should implement IDisposable yourself
completely, as we have done here. Do not mix finalizers with the special
destructor Destroy, as this mixture is not guranteed to work in the
future as the destructor implementation is tuned."
This has some implications for backwards-compatible code. It's nice to
see an explanation, though.
| Quote: | The main reason to use a weak reference is if you want the object to be
able to be collected despite having a reference. For example in a cache.
|
I've been using weak references in past to let node trees fall when the
root is let go of, even if someone had grabbed onto a child node. That
in itself is probably not a strong reason to use weak references, so
thanks for the prod, Marc :)
| Quote: | Marc Rohloff
marc rohloff at bigfoot dot com
|
-- Ritchie Annand
Senior Software Architect
http://www.malibugroup.com
http://nimble.nimblebrain.net
http://wiki.nimblebrain.net
|
|
| Back to top |
|
 |
Rudy Velthuis (TeamB) Guest
|
Posted: Sun Jan 11, 2004 10:57 am Post subject: Re: .NET reference-counting annoyances |
|
|
Ritchie wrote:
| Quote: | It seems that if you want things properly 'reference-counted' (at least
for the purposes of going away), you must implement pattern # 3.
Borland's VCL-for-.NET certainly does a lot of it, especially for
database access, controls and graphics.
|
..NET does not support reference counting in the way Delphi 7 did for
interfaces.
--
Rudy Velthuis (TeamB)
"We all agree that your theory is crazy, but is it crazy enough?"
- Niels Bohr (1885-1962)
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|