Как указать PowerShell на сбор мусора собрать объекты .NET, такие как XmlSchemaSet?

Я создал PowerShell script, который перебирает большое количество файлов XML Schema (.xsd) и для каждого создает объект .NET XmlSchemaSet, вызывает Add() и Compile(), чтобы добавить к нему схему, и распечатывает все ошибки проверки.

Этот script работает корректно, но где-то есть утечка памяти, из-за чего он потребляет гигабайты памяти, если запускается на 100. файлов.

В основном я делаю в цикле следующее:

$schemaSet = new-object -typename System.Xml.Schema.XmlSchemaSet
register-objectevent $schemaSet ValidationEventHandler -Action {
    ...write-host the event details...
}
$reader = [System.Xml.XmlReader]::Create($schemaFileName)
[void] $schemaSet.Add($null_for_dotnet_string, $reader)
$reader.Close()
$schemaSet.Compile()

(Полный script, чтобы воспроизвести эту проблему, можно найти в этом значении: https://gist.github.com/3002649. Просто запустите его и просмотрите увеличение использования памяти в Задаче Manager или Process Explorer.)

Вдохновленный некоторыми сообщениями в блоге, я попытался добавить

remove-variable reader, schemaSet

Я также попытался собрать $schema из Add() и сделать

[void] $schemaSet.RemoveRecursive($schema)

Кажется, что они имеют какой-то эффект, но все же есть утечка. Я предполагаю, что более старые экземпляры XmlSchemaSet по-прежнему используют память без сбора мусора.

Вопрос: Как правильно учить сборщик мусора, чтобы он мог вернуть всю память, используемую в коде выше? Или в целом: как я могу достичь своей цели с ограниченным объемом памяти?

Ответ 1

Microsoft подтвердила, что это ошибка в PowerShell 2.0, и заявляют, что это было разрешено в PowerShell 3.0.

Проблема заключается в том, что обработчик событий, зарегистрированный с использованием Register-ObjectEvent, не собирает мусор. В ответ на вызов службы поддержки Microsoft заявила, что

"имели дело с ошибкой в ​​PowerShell v.2. Проблема вызвана на самом деле из-за того, что экземпляры объекта .NET больше не являются выпущенный из-за того, что обработчики событий не выпущены сами. проблема больше не воспроизводится с помощью PowerShell v.3".

Лучшим решением, насколько я могу судить, является взаимодействие между PowerShell и .NET на другом уровне: полностью выполнить проверку в коде С# (встроенном в PowerShell script) и просто передать список объектов ValidationEventArgs. См. Фиксированное воспроизведение script на https://gist.github.com/3697081: script функционально корректен и не содержит никакой памяти.

(Спасибо Microsoft Support за помощь в поиске этого решения.)


Первоначально Microsoft предложила другое обходное решение, которое должно использовать $xyzzy = Register-ObjectEvent -SourceIdentifier XYZZY, а затем в конце сделать следующее:

Unregister-Event XYZZY
Remove-Job $xyzzy -Force

Однако, это обходное решение является функционально неправильным. Любые события, которые все еще находятся "в полете", теряются во время выполнения этих двух дополнительных заявлений. В моем случае это означает, что я пропускаю ошибки проверки, поэтому вывод моего script является неполным.

Ответ 2

После remove-variable вы можете попытаться принудительно собрать коллекцию GC:

[GC]::Collect()