Twisted + qtreactor:最后一个窗口关闭后如何清理?

1 投票
1 回答
1568 浏览
提问于 2025-04-16 14:35

我有一个使用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

撰写回答