除非您事先确切知道将要执行的代码是什么,否则使用 #1 settrace 来确定代码是否实际上仍然处于活动状态,这将是困难的,并且是会减慢执行代码的速度,可能会大大减慢,具体取决于关于代码的用途。请参阅halting problem。
选项#3 是最好的(与#4 半结合)。这就是单独流程的用途——单独工作,同时让第一个流程继续处理其他事情。设置起来并不难。
新进程 (P2) 不必包含对 GUI 中的对象的调用,应该分离关注点。这并不意味着他们不能互动。使用套接字对进行通信,例如,双方都使用pickle 来相互发送 python 对象。 GUI 主循环应该安排一个检查,例如50 毫秒,检查它的套接字,该套接字设置为非阻塞,用于来自 P2 的通信。然后 P2 可以根据需要向 GUI 发送消息,GUI 会对此做出响应。
P2 在新进程 P3 中执行代码(不是绝对必要,但更好的设置)。 P2 还检查来自 GUI 的命令(如果需要,定期检查),当在 GUI 中使用停止按钮时,诸如“停止执行脚本”之类的命令,然后 P2 可以执行os.kill(P3ID..) 或@987654326 @如果使用multiprocessing等。或者它也可以向GUI发送命令并接收它需要的数据,GUI将在最长50ms内响应(开始响应)。
下面的代码只是部分 sn-ps,根本没有经过测试,只是为了让您了解架构。最好分成不同的部分,例如一个SocketCom 类,用于包装每个套接字,它封装了使用pickle 将数据转换为字节,并使用底层套接字发送它,或者以阻塞或非阻塞模式接收select 的单个消息,使用底层套接字在消息传回之前接收和解包消息,等等。
通用代码
sGUI, s2 = socket.socketpair(socket.AF_UNIX, socket.SEQPACKET)
sGUI.setblocking(False) # for direct use, but will raise system-dependent errors, better to use select
P2 = multiprocessing.Process(target=P2process, args=(s2,codeToRun) )
def P2process (sock, codeToRun) :
# sock is the socket connected to GUI socket
P3 = multiprocessing.Process(target=P3process, args=(codeToRun,) ) # note, args is tuple
# block (up to you) for messages from/to GUI, check P3 periodically, etc.
# e.g. can do :
sock.sendall(pickle.dumps("sendMeX"))
X = pickle.loads(sock.recv(length))
# or e.g. a blocking loop that responds only to messages from GUI :
while True :
msg = pickle.loads(sock.recv(length))
if msg == 'status' :
sock.sendall(pickle.dumps(P3.is_alive()))
elif msg == 'stop' :
P3.terminate()
elif msg == 'newExecCode' :
newExecCode = pickle.loads(sock.recv(length))
elif msg == 'quit' :
P3.terminate()
break
...
def P3process (codeToRun) :
exec(codeToRun) # you should sandbox its context with custom globals & locals
# up to you how to solve the halting problem ;)
# user probably decides and GUI has 'stop' button if exec takes too long
图形用户界面:
GUI 可以随时使用sGUI 向 P2 发送命令,例如点击按钮。对于监听来自 P2 的消息,它看起来像这样:
def GUI_P2Com_loop (self) :
# this is called once, and at the end of the function, it registers itself as a
# callback to be called again after a timeout. It checks for messages from P2,
# and responds as necessary. It can also launch other processes to respond instead.
try :
reads, w, x = select.select([sGUI], [], [], 0) # 0 = non-blocking
if sGUI in reads :
msg = pickle.loads(sGUI.recv(length))
# got message from P2, do whatever
if msg == 'sendMeX' :
sGUI.sendall(pickle.dumps(X))
...
# so that the GUI can get on with responding to user interaction
# all GUI frameworks should have a function for registering a callback after timeout
# e.g. a tkinter widget would call :
self._job = widget.after(50, GUI_P2Com_loop) # where widget could be self if class extends widget
_job 被存储,以便可以使用例如取消通信循环。 tkinter 再次:
def cancel_com_loop (self) :
self.after_cancel(self._job)
请参阅tkinter effbot documentation。