GUI应用程序允许用户编写Python脚本,然后运行它(通过exec)。问题是如果用户错误地(我不关心恶意,只是用户方面的诚实编码错误)脚本包含无限循环,控件将永远不会返回到我的应用程序。由于GUI,键盘中断不起作用。
我已经确定了四种处理方法:
所以问题:如果我必须提供这个功能,上述哪种方法是所有邪恶中的较小者?还有其他技术,不是上面的4吗?
答案 0 :(得分:1)
一种方法是使用自定义multiprocessing.Manager
对象。它可以为您处理同步。 (链接:multiprocessing)
这是一个拥有单个实例的示例,其中多进程进程能够在其上调用方法。注意我没有使用实例状态,这留给了读者:)
原始代码正在传递" maths.add" (实例方法)到池,但方法是不可选择的。因此,我创建了全局" my_add",它接受一个数学实例( 可选),然后将一些数字加在一起并产生结果。
from multiprocessing import Pool
from multiprocessing.managers import BaseManager
class MathsClass(object):
def add(self, x, y):
return x + y
def mul(self, x, y):
return x * y
class MyManager(BaseManager):
pass
MyManager.register('Maths', MathsClass)
def my_add(mobj, *args):
return mobj.add(*args)
if __name__ == '__main__':
manager = MyManager()
manager.start()
maths = manager.Maths()
# pass the shared 'maths' object into each process
pool = Pool()
print pool.apply(my_add, [maths, 4, 3])
# print maths.add(4, 3) # prints 7
print pool.apply(my_add, [maths, 7, 8])
# print maths.mul(7, 8) # prints 56
7
15
答案 1 :(得分:1)
如何让Python代码保持主线程并产生一个工作线程,该线程将睡眠,然后用SIGINT中断主线程。
当用户进程完成时,您可以终止该工作程序。但是如果工作人员触发,你将获得相当于键盘中断的功能,这样你就可以捕获产生的异常并进行清理。
这样你就没有内存问题与主程序共享你的东西了,但你仍然会在一段固定的时间后以一种杀死失控的子程序的方式被戳戳。
答案 2 :(得分:0)
一般情况下,选项1被贬低,选项2或4和1会很好地结合起来。
你可以生成一个线程来安装你的settrace
回调,并继续在某种try / catch包装器中加载和执行脚本代码模块。
settrace
的实现不需要尝试分析,只需检查自己的线程的年龄,如果它太旧,则抛出异常。
然后包装器代码可以通知GUI超时。
我想这里的一部分原因是您可能不想直接使用exec
来执行用户的脚本,而是将模块加载到您exec
的内容中,这样您就可以打包它周围的Python代码,包括try / except块,以及安装可以注入提示的settrace或Timer
线程等。
答案 3 :(得分:0)
除非你事先知道将要执行的代码到底是什么,使用#1 settrace
来确定代码是否实际上仍处于活动状态,这将是困难的,是的会减慢执行代码的速度,可能会相当大,取决于代码的用途。请参阅halting problem。
选项#3是最好的(与#4半结合)。这就是单独的流程所针对的 - 分开工作,同时让第一个流程与其他事情相结合。设置并不困难。
新进程(P2)不必包含对GUI中对象的调用,应该将注意事项分开。这并不意味着他们无法互动。使用套接字对进行通信,例如,使用pickle
双方将python对象相互发送。 GUI主循环应该每个例如安排检查。 50ms,用于检查其套接字(设置为非阻塞),以便从P2进行通信。然后,P2可以向GUI发送消息(如果愿意,GUI将响应该消息)。
P2在新进程P3中执行代码(不是绝对必要,但更好的设置)。 P2还检查来自GUI的命令(如果需要,定期检查),诸如“停止执行脚本”之类的命令。当在GUI中使用停止按钮时,如果使用os.kill(P3ID..)
等,P2可以执行P3.terminate()
或multiprocessing
。或者它也可以向GUI发送命令并接收回它需要的数据,GUI将在最多50ms内响应(开始响应)。
下面的代码只是部分片段,根本没有经过测试,只是为了让您了解架构。最好分成不同的部分,例如用于包装每个套接字的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)