信号SIGINT(或SIGBREAK)由Windows按Ctrl+C发送到具有输入焦点的前台应用程序在这种情况下首先由cmd.exe处理批处理文件并提示用户是否应该终止批处理作业。
python.exe 将是进程层次结构中的下一个进程,但它没有收到此信号,因为cmd.exe 首先接收并处理它。请参阅 Microsoft 文档以获取 CTRL+C and CTRL+BREAK Signals。
可以使用%SystemRoot%\System32\taskkill.exe 不使用,使用选项/F 将CTRL+CLOSE Signal(控制台应用程序)分别发送到由其进程标识符(pid)标识的特定python.exe进程WM_CLOSE message(GUI 应用程序)。另请参阅Registering a Control Handler Function 的 Microsoft 文档。
看起来这个任务的批处理文件需要处理几个要求:
-
批处理文件不应仅用于启动两个 Python 进程,这些进程在 Python 收到终止或中断信号之前一直在无限运行,还应用于在用户按下 Ctrl+C 等键时终止两个 Python 进程 在为cmd.exe 处理批处理文件而打开的控制台窗口中。
-
这两个 Python 脚本将信息打印到用户应该能够阅读的标准输出流中。
-
在启动两个 Python 进程时可能已经在运行一个或多个 python.exe 进程,甚至可能发生另一个进程启动 python.exe 进程而 cmd.exe 正在处理批处理文件并启动两个 Python 进程.这需要特殊代码来获取由cmd.exe 处理批处理文件真正启动的两个 Python 进程的进程标识符。
-
最好两个 Python 进程都使用cmd.exe 的控制台窗口处理批处理文件以将它们的输出输出到标准输出流。
Windows 命令处理器解释的批处理文件很难满足这些要求,因为它并不是真正为此类复杂的进程管理任务而设计的。编写执行以下操作的第三个 Python 脚本会更好:
- 使用 sys.executable 获取解释第三个 Python 脚本的 Python 可执行文件的完整限定文件名。
- 使用
os.path.realpath(__file__) 获取当前执行的第三个Python 脚本的目录的完整路径,其中还包含Python 脚本gnss_reader.py 和imu_reader.py。
- 使用subprocess module在Windows上使用Windows内核库函数CreateProcess和结构STARTUPINFO打开两个子进程,Python可执行文件名和脚本文件路径分别与
gnss_reader.py连接imu_reader.py为参数并使用脚本文件路径指定当前工作目录。
这使第三个 Python 脚本可以完全控制在使用第三个 Python 脚本文件启动python.exe 时打开的控制台窗口的输出内容。当第三个 Python 脚本文件由于来自父进程或操作系统的信号或第三个 Python 脚本提供的用于终止的用户输入而自行终止时,第三个 Python 脚本还可以完全控制如何优雅地终止两个 Python 子进程。
我不是 Python 程序员,因此没有编写 Python 解决方案。
但我可以提供两种可能的批处理文件解决方案。
第一个批处理文件使用 WMIC 和 Create method of the Win32_Process class 启动两个 Python 进程,并使用适当的 Python 脚本文件作为参数,并将批处理文件的目录设置为两个 Python 进程的当前工作目录。
这个批处理文件解决方案的优点是wmic返回包含ProcessId的创建进程的信息,可以从FOR在后台启动的cmd.exe实例的标准输出中读取> 分别将cmd.exe处理批处理文件执行wmic命令行。
此解决方案的缺点是每个 Python 进程都会打开一个控制台窗口,因此此解决方案不满足第四个要求。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "usebackq skip=5 tokens=2 delims=;= " %%I in (`%SystemRoot%\System32\wbem\wmic.exe PROCESS CALL Create '"%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\gnss_reader.py"'^, "%~dp0"`) do set "PythonPIDs=/PID %%I" & goto StartSecond
:StartSecond
for /F "usebackq skip=5 tokens=2 delims=;= " %%I in (`%SystemRoot%\System32\wbem\wmic.exe PROCESS CALL Create '"%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\imu_reader.py"'^, "%~dp0"`) do set "PythonPIDs=%PythonPIDs% /PID %%I" & goto WaitTerminate
:WaitTerminate
echo/
%SystemRoot%\System32\choice.exe /C CE /N /M "Terminate with C or E ..."
%SystemRoot%\System32\taskkill.exe %PythonPIDs%
endlocal
pause
批处理文件的用户可以按C或E甚至Ctrl+C并回答cmd.exe的提示终止批处理作业使用 N 继续批处理文件处理,使用命令 TASKKILL 终止两个已启动的 Python 进程。
此解决方案已使用我编写的控制台应用程序进行了测试,该应用程序已启动而不是 python.exe,后者等待用户按下按键以自行终止,但被 TASKKILL 优雅终止.
第二个批处理文件解决方案期望在cmd.exe 启动批处理文件中指定的两个 Python 进程时,不会启动其他 python.exe 实例。批处理文件目录再次用作python.exe 的当前工作目录。
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "PythonPIDs= "
for /F "tokens=2" %%I in ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq python.exe" /NH 2^>nul') do set "PythonPIDs=!PythonPIDs!%%I "
start "" /B /D "%~dp0" "%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\gnss_reader.py"
start "" /B /D "%~dp0" "%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\imu_reader.py"
for /F "tokens=2" %%I in ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq python.exe" /NH 2^>nul') do if "!PythonPIDs: %%I =!" == "!PythonPIDs!" (set "PythonPIDs=!PythonPIDs!%%I ") else set "PythonPIDs=!PythonPIDs: %%I = !"
set "PythonPIDs=!PythonPIDs: = /PID !"
set "PythonPIDs=!PythonPIDs:~1,-5!"
echo/
%SystemRoot%\System32\choice.exe /C CE /N /M "Terminate with C or E ..."
%SystemRoot%\System32\taskkill.exe %PythonPIDs%
endlocal
pause
此批处理文件首先定义环境变量PythonPIDs,并使用空格分隔的所有正在运行的python.exe 进程的进程标识符列表。
接下来启动两个 Python 进程,将其输出到标准输出流写入在开始 cmd.exe 处理批处理文件时打开的控制台窗口。
然后第二次运行 TASKLIST 以删除环境变量 PythonPIDs 的字符串值中已经存在的所有 python.exe 进程标识符,并添加(希望只有两个)进程标识符启动 Python 进程。所以环境变量PythonPIDs最终应该用批处理文件启动的两个Python进程的进程标识符来定义。
在PythonPIDs 的字符串值中可能有另一个 Python PID,以防在启动批处理文件时已经运行的 Python 进程之一在两个 Python 进程由cmd.exe 启动的同时终止。对于两个启动的 Python 进程,可以使用与PythonPIDs 不同的环境变量。但是这个解决方案在任何情况下都要求在cmd.exe 启动两个Python 进程时没有另一个进程启动任何Python 进程。因此也可以假设在两个 Python 进程启动时没有 Python 进程终止。对于确定两个启动的 Python 进程的进程标识符,该解决方案永远不会完美。
两个启动的 Python 进程的终止以与第一个批处理文件相同的方式完成。
使用与第一个批处理文件解决方案相同的控制台应用程序在我的测试中正常终止无法正常工作,等待用户按键自行终止。这对我来说并不奇怪,因为控制台应用程序的两个实例使用与cmd.exe 处理批处理文件并启动choice.exe 相同的标准输入流。但我希望它适用于在运行时不处理标准输入的 Python 脚本。
命令START 的选项/B 并不意味着python.exe 确实在后台作为单独的进程启动,具有自己的标准输入、输出和错误流,而无需打开控制台窗口。选项/B 的使用导致python.exe 作为cmd.exe 的子进程启动,而无需打开新的控制台窗口,但使用cmd.exe 的标准输入、输出和错误流。因此,带有第三个 Python 脚本的 Python 解决方案很可能是最好的解决方案。
要了解所使用的命令及其工作原理,请打开command prompt 窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。
-
call /? ... 解释 %~dp0 ... 参数 0 的驱动器和路径,它是包含批处理文件的目录的完整路径。
choice /?
echo /?
endlocal /?
for /?
goto /?
if /?
pause /?
set /?
setlocal /?
start /?
taskkill /?
tasklist /?
wmic /?
wmic process /?
wmic process call /?
wmic process call create /?