GC and Finalize question

  • Thread starter Thread starter David L.S.
  • Start date Start date
D

David L.S.

After read about GC and Finalize, I try this test:



public void Test()

{

int num1;

FileStream stream1;

StreamWriter writer1;

for (num1 = 0; (num1 < 100); num1 += 1)

{

stream1 = new
FileStream(@"c:\temp\GC_Test\TestLOG.txt", FileMode.Append);

writer1 = new StreamWriter(stream1);

writer1.WriteLine("Now!!!");

writer1.Flush();

//writer1.Close();

stream1 = null;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}



This is what I'm trying to do:



Open a file and put the reference in stream1 variable.

After this, kill reference and ask for GC to collect objects and execute the
FileStream.Finalize method using WaitForPendingFinalizers method. The
FileStream.Finalize must free the file's handle without executing
FileStream.Close.



But, for my surprise, on second iteration, I get this exception:



An unhandled exception of type 'System.IO.IOException' occurred in
mscorlib.dll



Additional information: The process cannot access the file
"c:\temp\GC_Test\TestLOG.txt" because it is being used by another process.



What I'm doing wrong?



Is GC executing FileStream.Finalize method?



Is GC methods synchronous?
 
Error is not related to GC. You don't need to call explicitly GC. If you
remove calls to GC result will be absolutely same.
However, you forgot to close stream1 that's why you have error. You have to
put stream1.Close call before assigning it null.

HTH
Alex
 
AlexS said:
Error is not related to GC. You don't need to call explicitly GC. If you
remove calls to GC result will be absolutely same.
However, you forgot to close stream1 that's why you have error. You have to
put stream1.Close call before assigning it null.

The OP's point is that the FileStream finalizer should be closing it.

My guess is that the stream actually isn't eligible for garbage
collection at that stage - in debug mode at least - because the writer
still has a reference to it. (The JIT may work out in release mode that
the writer will be written to before it's read again, and is therefore
eligible for garbage collection, but it's more conservative in debug
mode in case you want to inspect the variable.)

I certainly *hope* the OP is just investigating the GC to better his
understanding, and wouldn't *actually* be relying on finalizers in
practice...
 
Your guess is absolutely right. GC cannot free both writer1 and stream1 in
original code. This doesn't depend on the mode - debug or release.
To eliminate exception either both stream1 and writer1 should be set to null
before calling GC, or Close should be explicitly called on both of them. Or
some variation should be used.
At the very minimum it should be

writer1 = null;
stream1 = null;

before GC calls.

Interesting that this simplistic example - not so many objects to free -
demonstrates (on my machine) that GC calls increase execution 2 and more
times depending on number of iterations. Explicit Close calls are fastest
way to release resources.
In real world applications where 000's of objects may float around heap, GC
calls like these will be terribly expensive and might not produce desired
results.

HTH
Alex
 
AlexS said:
Your guess is absolutely right. GC cannot free both writer1 and stream1 in
original code. This doesn't depend on the mode - debug or release.

It does. The sample code runs fine in release mode.
That's a fact hardly known about the GC: In release mode, it recognizes
whether a variable is used again later in code or not.
To eliminate exception either both stream1 and writer1 should be set to null
before calling GC,

As I said, in a release build, neither has to be set to null. Try yourself!
or Close should be explicitly called on both of them.

Which is better anyway...

Niki
 
Niki Estner said:
It does. The sample code runs fine in release mode.
That's a fact hardly known about the GC: In release mode, it recognizes
whether a variable is used again later in code or not.

I tend to believe this is effect of JIT and/or compiler optimizations, not
related to GC behavior. Can anybody add anything sensible?
Otherwise I have suspicion that debug mode by definition provides me with
"invalid" behavior and misleading results. Like profiling results, heap
allocations, exceptions raised etc. Seems to be too absurd. Is compiler
producing so much different code for debug and release?
Discussed sample is very illustrative in this respect
- should I conclude that exception in debug mode could be neglected? If same
sample "runs" in release mode?
or
- should I expect that if debug mode demonstrates exception - it will happen
sooner or later in release mode?
So, are debug results valid? Always or it depends? If "it depends", then how
should I debug my programs to predict real release mode behavior? GC here is
acting as black box - nobody knows what and when it does in details. So,
should we all use techniques as in example - let GC collect free objects in
release mode where it excels in garbage collection, while failing in debug
one - or should we expect that GC will fail in its duties in any case?

nice one...

"When you see sign "BULL" on the cage with tiger, don't believe your eyes".
 
I suggest that you use close the file stream. This is very important since a file stream is a wrapper around an unmanaged file handle in the OS. I agree with you that the destructor/finalization method for the file stream should close the open file handle, but it doesn't seem to be doing that. As a test try doing this:

using (FileStream stream1 = new
FileStream(@"c:\temp\GC_Test\TestLOG.txt", FileMode.Append))
{
writer1 = new StreamWriter(stream1);
writer1.WriteLine("Now!!!");

writer1.Flush();

writer1.Close();
}

The using statement will give the FileStream s definite scope.

Regards,

Les T.
 
I tend to believe this is effect of JIT and/or compiler optimizations, not
related to GC behavior. Can anybody add anything sensible?

No, this behaviour is documented. Have a look at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp,
especially the "Set Unneeded Member Variables to Null Before Making
Long-Running Calls" section. I've read a more detalied explanation somewhere
else, but couldn't find it on google right now.
Otherwise I have suspicion that debug mode by definition provides me with
"invalid" behavior and misleading results. Like profiling results, heap
allocations, exceptions raised etc. Seems to be too absurd. Is compiler
producing so much different code for debug and release?

The compiled code is more or less the same (if I look at it in Reflector),
but it's executed differently. I think the idea is that you might want to
have a look at local variables in a debugger aftter they're used for the
last time.
Discussed sample is very illustrative in this respect
- should I conclude that exception in debug mode could be neglected? If same
sample "runs" in release mode?

In a way, yes: The GC behaves deterministic and will always finalize the
stream. But the code is ugly and bad style, the fact that it only runs in
release mode doesn't make that any better
or
- should I expect that if debug mode demonstrates exception - it will happen
sooner or later in release mode?

In a real-world situation, relying on the GC in that way is error-prone. But
if you call it explicitly yourself (which is, as you already noted very very
very slow) I think it's "safe" - the way
double-linked-lists-to-function-pointers were "safe" in C: everything works
as long as you don't make the slightest mistake. If you do, everything will
crash and you'll never find out why.
So, are debug results valid? Always or it depends? If "it depends", then how
should I debug my programs to predict real release mode behavior?

I think they are valid. You can rely on them, i.e. you can assume a
well-running debug app will also run in release mode, and that's what really
matters. If you rely on deterministic GC behaviour, it's good the app
crashes in debug mode, because GC (under normal circumstances) doesn't
behave deterministic.
GC here is
acting as black box - nobody knows what and when it does in details.

Not really; You can always take a look at the shared source implementation.
AFAIK the GC is one of the really "shared" parts.
I did take a look, and I didn't understand a single line of the code. Pretty
heavy stuff.
Send me a summary if you're through ;-)
So,
should we all use techniques as in example - let GC collect free objects in
release mode where it excels in garbage collection, while failing in debug
one - or should we expect that GC will fail in its duties in any case?

Basically yes, and if you always close your streams, and dispose your
components, noone will see a difference.
To be honest: as long as I can't imagine a case where the debug version runs
fine, but the release doesn't, that can't scare me anyway...

Niki
 
Niki Estner said:
To be honest: as long as I can't imagine a case where the debug
version runs fine, but the release doesn't, that can't scare me
anyway...

That's easy - mutexes. People often use mutexes to determine when an
app is already running. If you use:

bool alreadyRunning = false;
Mutex mutex = ....;
// Don't refer to mutex after the line above
if (alreadyRunning)
{
...
}
else
{
Application.Run(...)
}


then in debug mode, it'll work as desired - the mutex will last for as
long as the program is running. In release, it'll get garbage collected
at some time, so you won't be able to detect that instance. To correct
this, you either need to make mutex a static variable, or use
GC.KeepAlive(mutex) at the end of the Main method (or wherever it is).

There are other nastier examples, but that'll do for now :)
 
Yes, I guess I gave that claim too little thought before typing it...
Still, I do think that the advantages of "GC's cleverness" outweigh the
disadvantages. The only trouble is that the
"things-will-get-collected-when-out-of-scope"-myth is so common in
developer's heads.

On the other hand, I probably didn't give those last two sentences enough
thought before typing either ;-)

Niki
 
Niki Estner said:
Yes, I guess I gave that claim too little thought before typing it...
Still, I do think that the advantages of "GC's cleverness" outweigh the
disadvantages.
Absolutely.

The only trouble is that the
"things-will-get-collected-when-out-of-scope"-myth is so common in
developer's heads.

Yup. The fact that people talk about *objects* going out of scope and
*variables* being garbage collected shows the amount of confusion of
concepts, IMO.
On the other hand, I probably didn't give those last two sentences enough
thought before typing either ;-)

LOL ;)
 
This work fine and prove (for me) that GC works.

I'll investigate more about differences between debug and release mode.

And don't worry. I'm using Close method in my release code.

Thanks.

AlexS said:
Your guess is absolutely right. GC cannot free both writer1 and stream1 in
original code. This doesn't depend on the mode - debug or release.
To eliminate exception either both stream1 and writer1 should be set to null
before calling GC, or Close should be explicitly called on both of them. Or
some variation should be used.
At the very minimum it should be

writer1 = null;
stream1 = null;

before GC calls.

Interesting that this simplistic example - not so many objects to free -
demonstrates (on my machine) that GC calls increase execution 2 and more
times depending on number of iterations. Explicit Close calls are fastest
way to release resources.
In real world applications where 000's of objects may float around heap, GC
calls like these will be terribly expensive and might not produce desired
results.

HTH
Alex


have
 
Back
Top