我注意到,当出现“停止”信号(例如按 CTRL+C< 时,在高级函数中实现 IDisposable
的对象不会被可靠地处理/kbd>) 在执行期间发送。当对象持有例如文件的句柄时,这会很痛苦。如果在不适当的时间收到停止信号,则句柄不会关闭,并且文件将保持锁定状态,直到 PowerShell session 关闭为止。
考虑以下类和函数:
class f : System.IDisposable {
Dispose() { Write-Host 'disposed' }
}
function g {
param( [Parameter(ValueFromPipeline)]$InputObject )
begin { $f = [f]::new() }
process {
try {
$InputObject
}
catch {
$f.Dispose()
throw
}
}
end {$f.Dispose()}
}
function throws {
param ( [Parameter(ValueFromPipeline)] $InputObject )
process { throw 'something' }
}
function blocks {
param ( [Parameter(ValueFromPipeline)] $InputObject )
process { Wait-Event 'bogus' }
}
想象一下 $f
持有一个文件的句柄,并在调用其 Dispose()
方法时释放它。我的目标是 $f
的生命周期与 g
的生命周期匹配。当通过以下方式调用 g
时,$f
会被正确处理:
g
'o' | g
'o' | g | throws
我可以说这么多,因为每个输出都已处理
。
但是,当在 g
下游执行时发送停止信号时,$f
不会被释放。为了测试这一点,我调用了
'o' | g | blocks
它在 blocks
内的 Wait-Event
处阻塞,然后我按 Ctrl+C 停止执行。在这种情况下,Dispose()
似乎没有被调用(或者,至少 dispose
没有写入控制台)。
在此类函数的 C# 实现中,我的理解是 StopProcessing()
收到停止信号后被调用来进行此类清理。但是,似乎没有类似于 StopProcessing
可用于高级功能的 PowerShell 实现。
如何确保在所有情况下(包括停止信号)都处理 $f
?
最佳答案
如果函数接受管道输入,则不能。
如果函数接受管道输入,我认为不可能有一种可靠的方法来实现这一点。原因是代码在管道上游执行时可能会发生以下任何情况:
中断
、继续
或抛出
- 终止错误
- 收到停止信号
当这些发生在上游时,函数的任何部分都不会进行干预。 begin{}
和 process{}
block 要么运行完成,要么根本不运行,而 end{}
block 可能会也可能会不被运行。我发现的最接近即时解决方案如下:
function g {
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
begin { $f = [f]::new() } # The local IDisposable is created when the pipeline is established.
process {
try
{
# flags to keep track of why finally was run
$success = $false
$caught = $false
$InputObject # output an object to exercise the pipeline downstream
# if we get here, nothing unusual happened downstream
$success = $true
}
catch
{
# we get here if an exception was thrown
$caught = $true
# !!!
# This is bad news. It's possible the exception will be
# handled by an upstream process{} block. The pipeline would
# survive and the next invocation of process{} would occur
# after $f is disposed.
# !!!
$f.Dispose()
# rethrow the exception
throw
}
finally
{
# !!!
# This finally block is not invoked when the PowerShell instance receives
# a stop signal while executing code upstream in the pipeline. In that
# situation cleanup $f.Dispose() is not invoked.
# !!!
if ( -not $success -and -not $caught )
{
# dispose only if finally{} is the only block remaining to run
$f.Dispose()
}
}
}
end {$f.Dispose()}
}
但是,根据评论,仍然存在未调用 $f.Dispose()
的情况。您可以单步执行this working example that includes such cases .
考虑使用像 usingObject {}
这样的模式。
如果我们将使用限制在负责清理的函数不接受管道输入的情况下,那么我们可以将生命周期管理逻辑分解为类似于 C# 的 using
block 的辅助函数。 Here is a proof-of-concept that implements such a helper function called usingObject
.这是一个示例,说明如何在使用 usingObject
实现 .Dispose()
的稳健调用时大大简化 g
:
# refactored function g
function g {
param (
[Parameter(ValueFromPipeline)]
$InputObject,
[Parameter(Mandatory)]
[f]
$f
)
process {
$InputObject
}
}
# usages of function g
usingObject { [f]::new() } {
g -f $_
}
usingObject { [f]::new() } {
'o' | g -f $_
}
try
{
usingObject { [f]::new() } {
'o' | g -f $_ | throws
}
}
catch {}
usingObject { [f]::new() } {
'o' | g -f $_ | blocks
}
关于powershell - 如何确保在停止信号时对高级函数的局部变量调用 Dispose()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46714132/