问题 如何指示PowerShell垃圾收集.NET对象,如XmlSchemaSet?


我创建了一个PowerShell脚本,它循环遍历大量XML Schema(.xsd)文件,并为每个文件创建一个.NET XmlSchemaSet 对象,电话 Add() 和 Compile() 向其添加架构,并打印出所有验证错误。

此脚本可以正常工作,但某处存在内存泄漏,如果在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()

(在这个要点中可以找到重现此问题的完整脚本: https://gist.github.com/3002649。只需运行它,并在任务管理器或Process Explorer中观察内存使用量的增加。)

受到一些博客帖子的启发,我尝试添加

remove-variable reader, schemaSet

我也试过拿起 $schema 从 Add() 和做

[void] $schemaSet.RemoveRecursive($schema)

这似乎有一些影响,但仍有泄漏。我假设是旧的实例 XmlSchemaSet 仍在使用内存而不进行垃圾回收。

问题是: 如何正确地教垃圾收集器它可以回收上面代码中使用的所有内存?或者更一般地说:如何通过有限的内存来实现我的目标?


2272
2018-06-27 09:15


起源



答案:


Microsoft已经确认这是PowerShell 2.0中的一个错误,他们声明这已在PowerShell 3.0中得到解决。

问题是使用Register-ObjectEvent注册的事件处理程序不是垃圾回收。在回应支持电话时,微软表示

“我们正在处理PowerShell v.2中的一个错误。问题是由此引起的   实际上是因为.NET对象实例不再存在   由于事件处理程序未被释放而被释放。该   PowerShell v.3“不再能重现问题”。

据我所知,最好的解决方案是在不同级别的PowerShell和.NET之间进行接口:在C#代码中完全进行验证(嵌入在PowerShell脚本中),然后传回一个列表 ValidationEventArgs 对象。请参阅固定的复制脚本 https://gist.github.com/3697081:该脚本在功能上是正确的,并且没有内存泄漏。

(感谢Microsoft支持帮助我找到此解决方案。)


最初微软提供了另一种解决方法,即使用 $xyzzy = Register-ObjectEvent -SourceIdentifier XYZZY,然后在最后执行以下操作:

Unregister-Event XYZZY
Remove-Job $xyzzy -Force

然而,这种解决方法在功能上是不正确的。任何仍在“飞行中”的事件在执行这两个附加语句时都会丢失。就我而言,这意味着我错过了验证错误,因此我的脚本输出不完整。


9
2017-07-16 15:38





之后 remove-variable 你可以尝试强制GC收集:

[GC]::Collect()

4
2018-06-27 09:24



这确实使增加更少,所以这可能有助于实践;但使用的内存量仍在逐渐增加。 - Marnix Klooster
添加一个 $reader.Dispose() 关闭后它有多帮助? - CB.
如中所述 另一个StackOverflow答案 这在PowerShell中是不可能直接实现的,因此没有必要 Close()意味着一个 Dispose() 根据推荐的Microsoft惯例。 - Marnix Klooster
@MarnixKlooster我使用powershell.exe.config加载.net 4.0框架,我可以做$ reader.dispose()。抱歉信息不好。 - CB.
没问题 - 请你详细说明你如何“使用powershell.exe.config加载.NET 4.0框架”作为这个问题的答案: stackoverflow.com/q/745956/223837?谢谢! - Marnix Klooster


答案:


Microsoft已经确认这是PowerShell 2.0中的一个错误,他们声明这已在PowerShell 3.0中得到解决。

问题是使用Register-ObjectEvent注册的事件处理程序不是垃圾回收。在回应支持电话时,微软表示

“我们正在处理PowerShell v.2中的一个错误。问题是由此引起的   实际上是因为.NET对象实例不再存在   由于事件处理程序未被释放而被释放。该   PowerShell v.3“不再能重现问题”。

据我所知,最好的解决方案是在不同级别的PowerShell和.NET之间进行接口:在C#代码中完全进行验证(嵌入在PowerShell脚本中),然后传回一个列表 ValidationEventArgs 对象。请参阅固定的复制脚本 https://gist.github.com/3697081:该脚本在功能上是正确的,并且没有内存泄漏。

(感谢Microsoft支持帮助我找到此解决方案。)


最初微软提供了另一种解决方法,即使用 $xyzzy = Register-ObjectEvent -SourceIdentifier XYZZY,然后在最后执行以下操作:

Unregister-Event XYZZY
Remove-Job $xyzzy -Force

然而,这种解决方法在功能上是不正确的。任何仍在“飞行中”的事件在执行这两个附加语句时都会丢失。就我而言,这意味着我错过了验证错误,因此我的脚本输出不完整。


9
2017-07-16 15:38





之后 remove-variable 你可以尝试强制GC收集:

[GC]::Collect()

4
2018-06-27 09:24



这确实使增加更少,所以这可能有助于实践;但使用的内存量仍在逐渐增加。 - Marnix Klooster
添加一个 $reader.Dispose() 关闭后它有多帮助? - CB.
如中所述 另一个StackOverflow答案 这在PowerShell中是不可能直接实现的,因此没有必要 Close()意味着一个 Dispose() 根据推荐的Microsoft惯例。 - Marnix Klooster
@MarnixKlooster我使用powershell.exe.config加载.net 4.0框架,我可以做$ reader.dispose()。抱歉信息不好。 - CB.
没问题 - 请你详细说明你如何“使用powershell.exe.config加载.NET 4.0框架”作为这个问题的答案: stackoverflow.com/q/745956/223837?谢谢! - Marnix Klooster