我是使用 COM 对象的新手,我已经阅读了我能理解的内容,但我似乎无法弄清楚这种行为。我怀疑在幕后发生了一些我不明白的明显事情。我正在使用 LogParser 自动处理 IIS 日志并在 Excel 中构建报告。我从编写的 PowerShell 模块中的以下代码块开始,以获取使用的最后一列:
$used = $WorkSheet.usedRange
$lastCell = $used.SpecialCells($xlCellTypeLastCell)
if($Axis -eq 'Row'){$tmpLast = $lastCell.Row}
if($Axis -eq 'Column'){$tmpLast = $lastCell.Column}
Release-Ref $used
$WorkSheet 是一个有效的工作表对象。此代码块在小型工作表上运行良好,但随着工作表大小的增加,执行时间会越来越长。如果床单太大,它会完全挂起。此外,它会开始挂起我尝试使用的其他应用程序,而我最终只是等待它完成,然后才能使用我的计算机。这不是资源问题,因为我在具有 24GB RAM 的 i7 上运行它。当我单步执行 PowerShell ISE (2.0) 中的代码时,我发现在我单步执行Release-Ref $used
. 我不确定为什么它只会在大型工作表的特定行上挂起。大由约 25K 行和 15-20 列定义,对于 Excel 可以处理的情况来说并不是那么大。这是 Release-Ref 函数:
function Release-Ref{
[CmdletBinding(DefaultParameterSetName='Single')]
param(
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Single')]
[System.__ComObject]$ref,
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Array')]
[System.__ComObject[]]$refs,
[Switch]$Final
)
if($Final){
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
if($PSCmdlet.ParameterSetName -eq 'Array'){
foreach($ref in $refs){
if($ref -ne $null){
[System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($ref) `
| Out-Null
}
}
}else{
if($ref -ne $null){
[System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($ref) `
| Out-Null
}
}
}
单步执行此功能时,我还注意到它也会滞后于if($ref -ne $null)
. 我根据这篇文章构建了有问题的代码块和函数,该文章声明“永远不要对 COM 对象使用 2 个点”,并展示了如何在 Mike Rosenblom 的回复之后正确清理 Excel 进程。我最初在自动化结束时遇到了 Excel 流程遗留问题,并尽我所能遵循了这一原则。我最终让 Excel 正确关闭,但某些报告的执行需要 20-40 分钟。
我终于移到了下面的代码块,替换了前面的代码:
$lastCell = $WorkSheet.UsedRange.SpecialCells($xlCellTypeLastCell)
if($Axis -eq 'Row'){$tmpLast = $lastCell.Row}
if($Axis -eq 'Column'){$tmpLast = $lastCell.Column}
我现在违反了 2 点原则,但这消除了挂起的呼叫,现在报告在几分钟内运行。Excel 仍在关闭,但不像以前那样立即关闭。我担心 Excel 现在可能会偶尔出现,但目前性能改进是值得的。
我的问题归结为:
- 我是否应该担心 Excel 将来会因为违反该部分代码中的 2 点原则而无法关闭?
- 而且,是什么解释了原始代码上“大型”工作表的性能下降?
我不会猜到它会是对 Release-Ref 函数的调用会很慢,但由于我对垃圾收集过程不太熟悉,所以它里面有一些东西。