Когда вы компилируете код .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
, а вместо того, чтобы исправить неправильный финализатор, но если «внутренний объект» был неуправляемым ресурсом, все могло бы быть иначе.
Теперь, что касается хуков клавиатуры, если вы храните свою ссылку в корне, например, в статическом поле, поле члена объекта, который также остается в живых, и т. Д., То оно не должно собираться неожиданно.