Files
wehub-resource-sync 39a086b41b
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 10.2 / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:08 +08:00

169 lines
5.1 KiB
Python

import sys
import subprocess
import threading
from typing import Optional, Callable, List, Any
from .process_manager import ProcessManager
class AsyncPythonRunner:
def __init__(self):
"""初始化异步Python运行器"""
self.process: Optional[subprocess.Popen] = None
self._output_threads: List[threading.Thread] = []
self._monitor_thread: Optional[threading.Thread] = None
self.returncode: Optional[int] = None
self._stdout_callbacks: List[Callable[[str], Any]] = []
self._stderr_callbacks: List[Callable[[str], Any]] = []
self._exit_callbacks: List[Callable[[int], Any]] = []
def register_callback(self,
stdout: Callable[[str], Any] = None,
stderr: Callable[[str], Any] = None,
complete: Callable[[int], Any] = None):
"""注册回调函数"""
if stdout:
self._stdout_callbacks.append(stdout)
if stderr:
self._stderr_callbacks.append(stderr)
if complete:
self._exit_callbacks.append(complete)
def _notify_callbacks(self,
stream_type: str,
content: str):
"""通知已注册的回调"""
if stream_type == 'stdout':
for callback in self._stdout_callbacks:
callback(content)
elif stream_type == 'stderr':
for callback in self._stderr_callbacks:
callback(content)
def start(self,
script_path: str,
args: List[str] = None,
python_args: List[str] = None,
cwd: str = None):
"""
启动子进程
:param script_path: 要执行的Python脚本路径
:param args: 传递给脚本的参数
:param python_args: 传递给Python解释器的参数
:param cwd: 设置工作目录
"""
if self.process is not None:
raise RuntimeError("Process is already running")
python_path = sys.executable
cmd = [python_path]
# 添加Python解释器参数
if python_args:
cmd.extend(python_args)
# 添加脚本路径和参数
cmd.append(script_path)
if args:
cmd.extend(args)
# 启动子进程
self.process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8',
text=True,
bufsize=1,
universal_newlines=True,
cwd=cwd
)
# 启动输出读取线程
self._start_reader_thread('stdout', self.process.stdout)
self._start_reader_thread('stderr', self.process.stderr)
# 启动监控线程
self._monitor_thread = threading.Thread(
target=self._monitor_process,
daemon=True
)
self._monitor_thread.start()
def _start_reader_thread(self,
stream_type: str,
stream):
"""启动输出读取线程"""
def reader():
for line in iter(stream.readline, ''):
self._notify_callbacks(stream_type, line)
stream.close()
thread = threading.Thread(
target=reader,
daemon=True
)
thread.start()
self._output_threads.append(thread)
def _monitor_process(self):
"""监控进程状态"""
self.returncode = self.process.wait()
for callback in self._exit_callbacks:
callback(self.returncode)
def is_running(self) -> bool:
"""检查子进程是否在运行"""
return self.returncode is None
def stop(self):
"""停止子进程"""
if self.process:
ProcessManager.instance().terminate_by_pid(self.process.pid)
self.process = None
def wait(self, timeout: float = None) -> bool:
"""等待进程结束"""
if self._monitor_thread:
self._monitor_thread.join(timeout)
return not self._monitor_thread.is_alive()
return True
# 使用示例
if __name__ == "__main__":
def on_stdout(line: str):
print(f"[STDOUT] {line}", end='')
def on_stderr(line: str):
print(f"[STDERR] {line}", end='', file=sys.stderr)
def on_exit(code: int):
print(f"\n[EXIT] Process finished with code {code}")
# 创建运行器
runner = AsyncPythonRunner()
# 注册回调
runner.register_callback(
stdout=on_stdout,
stderr=on_stderr,
complete=on_exit
)
# 启动脚本 (带参数示例)
runner.start(
script_path="path/to/your_script.py",
args=["--param1", "value1", "--flag"],
python_args=["-u"] # 无缓冲模式
)
# 主线程继续执行其他任务...
try:
while runner.is_running():
# 模拟其他工作
threading.Event().wait(0.5)
except KeyboardInterrupt:
print("\nStopping process...")
runner.stop()
# 等待进程结束
runner.wait()
print("Main thread exiting")