Что-то не так со спамом GC.KeepAlive (KeyboardHookPointer)? - PullRequest
4 голосов
/ 12 апреля 2010

GC.KeepAlive ()

Ссылается на указанный объект, что делает его непригодным для сбора мусора с начала текущей подпрограммы до точки вызова этого метода.

Не совсем уверен в том, что GC.KeepAlive делает, кроме простого сохранения ссылки, чтобы сборщик мусора не собирал объект. Но не вызывает ли объект GC.KeepAlive () для объекта постоянное удержание объекта от его сбора? Или вам нужно повторять вызов GC.KeepAlive () время от времени (и если да, то как часто)? Я хочу, чтобы мой хук клавиатуры оставался в живых.

Ответы [ 2 ]

10 голосов
/ 12 апреля 2010

Когда вы компилируете код .NET для цели выпуска, сборщик мусора действительно агрессивен, то есть потенциально может быть.

Возьмите этот пример:

public void Test()
{
    FileStream stream = new FileStream(....);
    stream.Write(...);
    SomeOtherMethod();
}

В этом примере после возврата вызова Stream.Write этот метод больше не используется для переменной stream и, таким образом, считается вне области действия, что означает, что объект FileStream теперь имеет право на коллекция. Если сборщик мусора запускается во время вызова SomeOtherMethod, этот объект потока может быть собран.


Редактировать : Как указывает @ Грег Бич в комментариях, объект может быть собран, даже если в настоящее время на нем выполняется метод экземпляра.

Например, скажем, код в FileStream.Write выглядит следующим образом:

public void Write(byte[] buffer, ...)
{
    IntPtr unmanagedHandle = _InternalHandleField;
    SomeWindowsDll.WriteBuffer(unmanagedHandle, ...);
    ...

здесь метод сначала захватывает внутреннее поле, содержащее неуправляемый дескриптор. Объект не будет собран до того, как это произошло. Но если это последнее поле объекта или последний метод экземпляра объекта, доступ к которому осуществляется в режиме записи перед переходом только к вызовам P / Invoke, то объект теперь пригоден для сбора.

И поскольку FileStream, вероятно, имеет неуправляемый дескриптор файла (я говорю, вероятно, я не знаю), он, вероятно, также имеет финализатор, поэтому неуправляемый дескриптор файла может быть закрыт до завершения вызова WriteBuffer , То есть, если предполагаемая реализация .Write такая же, как указано выше, скорее всего, нет.


Если вы хотите предотвратить это, активируйте потоковый объект на время вызова на SomeOtherMethod по любой причине, вам нужно вставить код после вызова, который каким-то образом «использует» объект. Если вы не хотите вызывать метод или читать свойство объекта, вы можете использовать искусственный метод «использования» GC.KeepAlive, чтобы сделать это:

public void Test()
{
    FileStream stream = new FileStream(....);
    stream.Write(...);
    SomeOtherMethod();
    GC.KeepAlive(stream);
}

Метод ничего не делает, но он был помечен атрибутами, чтобы его нельзя было оптимизировать. Это означает, что переменная потока теперь используется для вызова SomeOtherMethod, и сохраненный в ней объект FileStream не будет собран в течение этого времени.

Вот и все, что он делает. GC.KeepAlive не оставляет постоянной отметки на чем-либо, он не изменяет время жизни объекта после вызова, он просто добавляет код, который «использует» переменную в какой-то момент, что не позволяет сборщику мусора собирать объект.

Я узнал об этом методе, или, скорее, о сути этого метода, о сложном пути с некоторым кодом, который выглядел так:

public void Test()
{
    SomeObjectThatCanBeDisposed d = new SomeObjectThatCanBeDisposed();
    SomeInternalObjectThatWillBeDisposed i = d.InternalObject();
    CallSomeMethod(i);
}

Обратите внимание, что я создаю экземпляр объекта, реализующего IDisposable, и в исходном случае он также реализовал финализатор. Затем я «выловил» объект, за которым он следил, и способ, которым одноразовый объект был настроен (неправильно), заключался в том, что после запуска финализатора он также будет избавляться от внутренних объектов (что плохо). В приведенном выше коде d получает право на сбор после извлечения внутреннего объекта, а после сбора объекта он завершает себя, удаляя извлеченный мной объект. Это означает, что во время вызова CallSomeMethod объект, переданный методу, умер, и я не мог понять, почему.

Конечно, исправление для приведенного выше кода состояло не в том, чтобы использовать GC.KeepAlive, а вместо того, чтобы исправить неправильный финализатор, но если «внутренний объект» был неуправляемым ресурсом, все могло бы быть иначе.

Теперь, что касается хуков клавиатуры, если вы храните свою ссылку в корне, например, в статическом поле, поле члена объекта, который также остается в живых, и т. Д., То оно не должно собираться неожиданно.

2 голосов
/ 12 апреля 2010

GC.KeepAlive (а также GC.Collect) не должны использоваться в рабочем коде, потому что он не решает никаких проблем, он просто создает больше условий гонки. Они полезны только для диагностики проблемы.

Если у вас есть управляемый объект, используемый собственным кодом, и сборщик мусора не может видеть, что он все еще используется, вы должны использовать GCHandle.Alloc .

РЕДАКТИРОВАТЬ: Некоторые подтверждающие доказательства:

GC.KeepAlive не очень хорош, и он определенно хорош только тогда, когда использование нативного кода гарантированно прекращается перед возвратом, что не относится к хуку клавиатуры.

...