GC.KeepAlive()
References the specified object, which makes it ineligible for garbage collection from the start of the current routine to the point where this method is called.
Not really sure about what GC.KeepAlive does other than simply store a reference so the Garbage Collector doesn't collect the object. But does calling GC.KeepAlive() on an object permanently keep an object from being collected? Or do you have to re-call GC.KeepAlive() every so often (and if so, how often)? I want to keep my keyboard hook alive.
When you compile .NET code for Release target, the garbage collector is really aggressive, that is, it has the potential to be.
Take this example:
public void Test()
{
FileStream stream = new FileStream(....);
stream.Write(...);
SomeOtherMethod();
}
In this example, once the call to Stream.Write
has returned, this method has no more use for the stream
variable, and is thus considered out of scope, which means that the FileStream
object is now eligible for collection. If the garbage collector runs during the call to SomeOtherMethod
that stream object might get collected.
Edit: As @Greg Beech points out in the comments, an object can be collected even if an instance method is currently executing on it.
For instance, let's say the code in FileStream.Write
looks like this:
public void Write(byte[] buffer, ...)
{
IntPtr unmanagedHandle = _InternalHandleField;
SomeWindowsDll.WriteBuffer(unmanagedHandle, ...);
...
here, the method first grabs the internal field containing an unmanaged handle. The object will not be collected before this has happened. But, if this is the last field of the object, or the last instance-method of the object, being accessed in Write, before transitioning over to just P/Invoke calls, then the object is now eligible for collection.
And since FileStream probably has a unmanaged file handle (I say probably, I don't know), it probably has a finalizer as well, so the unmanaged file handle is in risk of being closed before the call to WriteBuffer has completed. That is, if the supposed implementation of .Write
is as above, which it most likely isn't.
If you wish to prevent this, make the stream object live for the duration of the call to SomeOtherMethod
for whatever reason, you need to insert code after the call that "uses" the object in some way. If you don't want to call a method or read a property on the object, you can use the artificial "usage" method of GC.KeepAlive
to do it:
public void Test()
{
FileStream stream = new FileStream(....);
stream.Write(...);
SomeOtherMethod();
GC.KeepAlive(stream);
}
The method does nothing, but it has been flagged with attributes so that it won't be optimized away. This means that the stream variable is now in use for the duration of the call to SomeOtherMethod
and the FileStream
object stored in it will not get collected during that time.
That's all it does. GC.KeepAlive
leaves no permanent mark on anything, it does not alter the lifetime of the object after being called, it simply adds code that "uses" the variable at some point, which prevents the garbage collector from collecting the object.
I learned about this method, or rather, the point of this method, the hard way with some code that looked like this:
public void Test()
{
SomeObjectThatCanBeDisposed d = new SomeObjectThatCanBeDisposed();
SomeInternalObjectThatWillBeDisposed i = d.InternalObject();
CallSomeMethod(i);
}
Notice that I construct an instance of an object implementing IDisposable
, and in the original case, it also implemented a finalizer. I then "fished" out an object it kept track of, and the way the disposable object was set up (incorrectly) was that once the finalizer ran, it would also dispose of internal objects (which is bad). In the above code, d becomes eligible for collection once the internal object has been extracted, and once the object is collected, it finalizes itself by disposing of the object I extracted. This means that during the call to CallSomeMethod
, the object passed to the method died and I couldn't understand why.
Of course, the fix for the above code was not to use GC.KeepAlive
, but instead to fix the incorrect finalizer, but if the "internal object" had been an unmanaged resource, things might've been different.
Now, as for your keyboard hook, if you store your reference in a root, like a static field, a member field of an object that is also kept alive, etc. then it should not be collected out of the blue.
GC.KeepAlive (as well as GC.Collect) shouldn't be used in production code, because it doesn't solve any problems, it just creates more race conditions. They're only useful for diagnosing a problem.
If you have a managed object being used by native code so the garbage collector can't see it is still being used, you should be using GCHandle.Alloc.
EDIT: Some supporting evidence:
GC.KeepAlive isn't good for very much, and it's definitely only good when the usage by native code is guaranteed to end before returning, which isn't the case for a keyboard hook.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With