考虑以下背景:
clean{}
运行。Stop-Process
“直接跳杀”甚至没有尝试标准化但不可靠的请求终止方法,如WM_CLOSE
和GenerateConsoleCtrlEvent
,如这里所述。SIGTERM
)的移植程序在 Windows 上却无法成功执行相同操作。 也许这条评论最简洁地总结了这种情况。现在考虑运行我的一些代码的 PowerShell 会话。 我可以在控制台上按 CTRL+C,执行
clean{}
块,通过任何活动的 Dispose()
和其他对象树进行级联 Cmdlet
调用,并且该过程最终会优雅地停止清理工作已完成。 我想从另一个进程中调用类似程度的有序停止。
我怎样才能以可靠的方式实现这一目标?
此技术导致
pwsh.exe
的目标实例使用 EventWaitHandle
监听特定的命名事件。 当引发该事件时,处理程序通过 CTRL_C_EVENT
引发
GenerateConsoleCtrlEvent
。 这似乎与在控制台按 CTRL+C 具有相同的效果。 优点是另一个进程可以获取并引发该事件,除了将目标的进程 ID 插入事件名称模式之外,无需任何先决条件。
原则上,可以在
Stop-Process
之前有条件地调用 pwsh
实例,方法是测试 TryOpenExisting()
来查看目标是否已注册句柄,如果已注册,则引发事件并允许目标有一些时间来注册句柄。优雅地关闭。 如果目标进程在超时后没有关闭,则可以调用通常的Stop-Process
来强制终止。
要使用此技术,需要在
<registration>
的实例中调用 pwsh.exe
标签中的部分,以便该实例能够优雅地响应信号。 然后可以调用 <signal>
标签中的部分(插入了正确的进程 ID)来指示实例正常停止。
下面的代码执行以下操作:
创建名为
remote.ps1
的脚本,并启动运行该脚本的 pwsh.exe
的独立实例。 然后,该“远程”PowerShell 实例执行以下操作:
pwsh_$PID_CTRL_C_EVENT_78008808-fb42-4ce6-b8bb-cfe8135a0914
的事件,通过 CTRL_C_EVENT
引发 GenerateConsoleCtrlEvent
原始脚本在创建“远程”PowerShell 实例后继续并执行以下操作:
pwsh_$PID_CTRL_C_EVENT_78008808-fb42-4ce6-b8bb-cfe8135a0914
事件请注意,运行演示代码会输出
begin
clean
表示
end{}
块被信号中断并且没有完成,但是clean{}
块确实完成了。
{
begin { 'begin' | Set-Content .\log.txt }
end {
# <registration>
$remote_ctrl_c_event =
[System.Threading.EventWaitHandle]::new(
$false,
'ManualReset',
"pwsh_$PID`_CTRL_C_EVENT_78008808-fb42-4ce6-b8bb-cfe8135a0914")
Add-Type @'
namespace n { public static class c {
[System.Runtime.InteropServices.DllImport("KERNEL32.dll",ExactSpelling=true,SetLastError=true)]
public static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent,int dwProcessGroupId);
public static void RegisterCtrlC(
System.Threading.WaitHandle handle
) {
System.Threading.ThreadPool.RegisterWaitForSingleObject(
handle,
(s,to) => GenerateConsoleCtrlEvent(0,0),
null,-1,true);
}
}}
'@
[n.c]::RegisterCtrlC($remote_ctrl_c_event)
# </registration>
Start-Sleep -Seconds 1000
'end' | Add-Content .\log.txt
}
clean { 'clean' | Add-Content .\log.txt }
} |
Set-Content .\remote.ps1
$remote_process = Start-Process -FilePath pwsh.exe -ArgumentList .\remote.ps1 -PassThru
Start-Sleep -Seconds 2
# <signal>
$remote_ctrl_c_event =
[System.Threading.EventWaitHandle]::new(
$false,
'ManualReset',
"pwsh_$($remote_process.Id)_CTRL_C_EVENT_78008808-fb42-4ce6-b8bb-cfe8135a0914"
)
$remote_ctrl_c_event.Set() | Out-Null
# </signal>
while (Get-Process -Id $remote_process.Id -ea si) {
Start-Sleep -Milliseconds 10
}
Get-Content .\log.txt