views:

127

answers:

2

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.

+6  A: 

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.

Lasse V. Karlsen
Great. Thanks for the quick reply.
Alex
great answer, thanks Lasse.
Segfault
Good explanation, but one small correction... an object can be collected even while an instance method is executing on it, once the method has extracted the required state from the object. So the `FileStream` object may be collected even before the call to `Write` has returned. See http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx for more details.
Greg Beech
@Greg, correct, let me augment my answer.
Lasse V. Karlsen
+1  A: 

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.

Ben Voigt
That's a very strong statement to make without providing any references or specifics to back it up. Care to elaborate?
Greg Beech
Just reading the italicized part of the original question shouldn't give you warm fuzzies about the approach. In this case, all the OA's concerns are warranted. GCHandle is the correct way to prevent collection "until further notice". But I'll find you some references in a minute.
Ben Voigt