如何确保在停止信号时对高级函数的局部变量调用 Dispose()?
How can I ensure Dispose() is called on an advanced function's local variable on stop signal?
我注意到当 "stop" 信号(例如按 CTRL+C) 在执行期间发送。当对象持有一个文件的句柄时,这会很痛苦。如果在不合时宜的时间收到停止信号,句柄不会关闭,文件将保持锁定状态,直到 PowerShell 会话关闭。
考虑以下 class 和函数:
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
的生命周期相匹配。 $f
通过以下方式调用 g
时正确处理:
g
'o' | g
'o' | g | throws
我能说的差不多,因为每个输出都 disposed
。
当在g
的下游执行时发送停止信号,但是,$f
不被处理。为了测试这一点,我调用了
'o' | g | blocks
which blocks at the Wait-Event
inside blocks
, then I pressed Ctrl+C停止执行.在那种情况下,Dispose()
似乎没有被调用(或者,至少 disposed
没有写入控制台)。
在此类函数的 C# 实现中,我的理解是 StopProcessing()
在停止信号上被调用以进行此类清理。但是,似乎没有可用于高级功能的 PowerShell 实现的 StopProcessing
模拟。
如何确保 $f
在所有情况下都得到处理,包括停止信号?
看来您只需添加一个 finally{} 块并将其放置在那里即可。您可能还想考虑设置 ErrorActionPreference,因为您正在进行自定义错误处理。
$ErrorActionPreference = 'SilentlyContinue'
try
{
try
{
1/0
}
catch
{
throw New-Object System.Exception("Exception!")
}
finally
{
"You can dispose here!"
}
}
catch
{
$_.Exception.Message | Write-Output
}
如果函数接受管道输入,则不能。
如果函数接受管道输入,我认为实现这一点的可靠方法是不可能的。原因是当代码在管道上游执行时,可能会发生以下任何情况:
break
、continue
或 throw
- 终止错误
- 收到停止信号
当这些发生在上游时,功能的任何部分都不会被干预。 begin{}
和 process{}
块要么 运行 完成,要么根本没有 运行,end{}
块可能是也可能不是 运行。我找到的最接近 on-point 的解决方案如下:
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 {}
这样的模式。
如果我们将使用限制在负责清理的函数不接受管道输入的情况下,那么我们可以将 lifetime-management 逻辑提取到类似于 C# 的 using
块的辅助函数中。 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
}
我注意到当 "stop" 信号(例如按 CTRL+C) 在执行期间发送。当对象持有一个文件的句柄时,这会很痛苦。如果在不合时宜的时间收到停止信号,句柄不会关闭,文件将保持锁定状态,直到 PowerShell 会话关闭。
考虑以下 class 和函数:
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
的生命周期相匹配。 $f
通过以下方式调用 g
时正确处理:
g
'o' | g
'o' | g | throws
我能说的差不多,因为每个输出都 disposed
。
当在g
的下游执行时发送停止信号,但是,$f
不被处理。为了测试这一点,我调用了
'o' | g | blocks
which blocks at the Wait-Event
inside blocks
, then I pressed Ctrl+C停止执行.在那种情况下,Dispose()
似乎没有被调用(或者,至少 disposed
没有写入控制台)。
在此类函数的 C# 实现中,我的理解是 StopProcessing()
在停止信号上被调用以进行此类清理。但是,似乎没有可用于高级功能的 PowerShell 实现的 StopProcessing
模拟。
如何确保 $f
在所有情况下都得到处理,包括停止信号?
看来您只需添加一个 finally{} 块并将其放置在那里即可。您可能还想考虑设置 ErrorActionPreference,因为您正在进行自定义错误处理。
$ErrorActionPreference = 'SilentlyContinue'
try
{
try
{
1/0
}
catch
{
throw New-Object System.Exception("Exception!")
}
finally
{
"You can dispose here!"
}
}
catch
{
$_.Exception.Message | Write-Output
}
如果函数接受管道输入,则不能。
如果函数接受管道输入,我认为实现这一点的可靠方法是不可能的。原因是当代码在管道上游执行时,可能会发生以下任何情况:
break
、continue
或throw
- 终止错误
- 收到停止信号
当这些发生在上游时,功能的任何部分都不会被干预。 begin{}
和 process{}
块要么 运行 完成,要么根本没有 运行,end{}
块可能是也可能不是 运行。我找到的最接近 on-point 的解决方案如下:
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 {}
这样的模式。
如果我们将使用限制在负责清理的函数不接受管道输入的情况下,那么我们可以将 lifetime-management 逻辑提取到类似于 C# 的 using
块的辅助函数中。 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
}