powershell - 如何确保在停止信号时对高级函数的局部变量调用 Dispose()?

标签 powershell idisposable powershell-cmdlet object-lifetime

我注意到,当出现“停止”信号(例如按 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/

相关文章:

powershell - 为什么有些数组在 PowerShell 中相互镜像?

powershell - PowerShell ISE和PowerShell.Exiting事件

powershell - filterhashtable通过事件代码过滤

c# - 在 Windows 窗体中为 Font 调用 dispose()

c# - ID一次性示例

powershell - 我应该使用什么来代替 Select-AzureSubscription?

powershell - 使用 "dot"变量名称的数组设置 PSObject 路径

c# - 调用 Imaging.CreateBitmapSourceFromHIcon 后可以安全地处理图标吗?

powershell - 使用 powershell 获取 Azure 账单信息?

sql-server - 使用 Powershell 从存储过程返回的数据(大型 XML 文件)从 ReportServer 输出 XML 时如何停止截断数据