Twisted + qtreactor:最后一个窗口关闭后如何清理?
我有一个使用Twisted和PyQt的应用程序,它可以连接到一些远程资源。当用户关闭窗口时,我想关闭所有的连接,尽量做到优雅地关闭,如果不行就强制关闭。
问题是,当我准备关闭连接时,似乎反应器(reactor)已经不再工作了,导致我无法进行关闭操作。
这是我的应用程序代码:
# Create app and connect the Twisted/Qt reactors
app = QApplication(sys.argv)
qtreactor.qt4reactor.install()
# Shutdown Twisted when window is closed
@defer.inlineCallbacks
def stop():
print "="*40, "Closing connections..."
yield closeConnections()
print "="*40, "closed."
print "="*40, "Stopping reactor..."
reactor.stop()
print "="*40, "stopped."
app.connect(app, SIGNAL("lastWindowClosed()"), stop)
reactor.runReturn()
rc = app.exec_()
exit(rc)
这是我简化后的清理代码:
@defer.inlineCallbacks
def closeConnections():
for connection in connections:
print "Closing connection #%s" % connection
yield threads.deferToThread(popen("/foo/bar/cleanup %s" % connection))
print "Connection closed."
我能看到第一个打印语句被执行了,但我从来没有看到第二个打印语句,也没有多次进入循环。
我的分析是否正确?问题是反应器已经停止工作,所以我无法收到来自threads.deferToThread的反馈吗?还是有其他问题?另外,我该如何解决这个问题呢?
谢谢,
乔纳森
1 个回答
2
我不太清楚lastWindowClosed()这个信号到底是什么时候触发的。不过,即使它触发得比较早,在反应器关闭之前(这会让你无法执行你想做的事情),我相信PyQt也不知道该如何处理你在stop
函数中返回的Deferred。这意味着,关闭过程会继续进行,而你的异步清理代码却在尝试运行。很可能在你的网络关闭操作还没开始之前,图形界面的关闭就已经完成了。
所以,建议你使用reactor.addSystemEventTrigger('before', 'shutdown', stop)
。我不确定这个会比lastWindowClosed()稍早还是稍晚触发,但它会在反应器仍然可用的时候运行,并且会关注你函数返回的Deferred。实际上,关闭过程会被暂停,直到这个Deferred触发。这给了你足够的时间来完成清理工作。
另外,你不应该使用threads.deferToThread(popen("/foo/bar/cleanup %s" % connection))
,原因如下:
- 你需要传递一个可调用的对象给
deferToThread
,而不是调用这个可调用对象后的结果。按照现在的写法,你的代码是在反应器线程中运行popen,并把一个文件对象传递给线程去调用(这显然是没有意义的) - 混合使用线程和子进程是有风险的。大多数时候你可能能侥幸逃过,但我不太确定。
reactor.spawnProcess
可以让你在不阻塞、不使用线程的情况下运行子进程,也不需要担心混合线程和进程的问题。如果你不需要spawnProcess
的所有功能(看起来你并不需要),可以参考twisted.internet.utils.getProcessOutput
。