twisted + gtk:我应该在线程中运行GUI,还是在反应器线程中?
根据我对twisted的理解,在反应器线程中运行的任何东西都不应该阻塞。所有会导致阻塞的操作都应该交给其他线程去处理,等它们完成后再把结果回调到反应器线程。
那么,这个原则也适用于gtk相关的内容吗?比如说,如果连接失败了,我想显示一个“连接失败”的消息,我应该这样做:
def connectionFailed(self, reason):
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_CLOSE,
message_format="Could not connect to server:\n%s" % (
reason.getErrorMessage()))
dlg.run()
还是这样:
def connectionFailed(self, reason):
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_CLOSE,
message_format="Could not connect to server:\n%s" % (
reason.getErrorMessage()))
reactor.callInThread(dlg.run)
或者这样:
def connectionFailed(self, reason):
def bloogedy():
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_CLOSE,
message_format="Could not connect to server:\n%s" % (
reason.getErrorMessage()))
dlg.run()
reactor.callInThread(bloogedy)
?
编辑:哦,好的,后面两个方法真的不行。所以我想答案是第一个。那么我的问题是:为什么?这看起来会阻塞反应器线程。
3 个回答
虽然不推荐也不被支持,但在Twisted 10.x版本中,似乎可以通过以下代码继续使用gtk.main() / dialog.run()。
gobject.idle_add(lambda *x: reactor.runUntilCurrent())
reactor.startRunning()
dialog.run()
根据我在使用Gtk+的经验,最好的做法是把图形界面(GUI)放在一个单独的线程里运行。你可以通过在Gtk+的主循环中运行一些函数来和GUI线程进行沟通,这个可以用到一个叫做 idle_add 的函数。我对reactor不太了解,但从你的例子来看,似乎也可以用类似的方式和GUI进行沟通。
比如,可以这样做(抱歉,我没有测试过这段代码):
def connectionFailed(self, reason):
def frob():
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_CLOSE,
message_format="Could not connect to server:\n%s" % (
reason.getErrorMessage()))
dlg.run()
gobject.idle_add(frob)
(除了这段代码,gtk.main 还需要在自己的线程中运行)
这样会在Gtk+线程中运行frob函数,并且不会阻塞reactor线程。
这段代码会在一个单独的线程中启动Gtk+:
import threading
import pygtk
pygtk.require('2.0')
import gtk
import gobject
def gtk_thread():
gtk.main()
threading.Thread(target=gtk_thread)
如果你需要一些复杂的GUI交互,你就得使用 继续传递风格 来编程。
编辑: 添加了在单独线程中运行Gtk+的例子。
你的问题其实和线程以及图形界面没有直接关系。你应该始终在同一个线程中使用Twisted和GTK,没必要分开。
你的问题出在你使用了gtk.Dialog.run()
。这个接口是你绝对不应该使用的,无论是否使用Twisted。它会运行一个可重入的主循环,这样会导致你当前的事件处理程序被阻塞,但其他事件处理程序可以在更深的层次上执行。GTK对可重入主循环的支持非常好,但Twisted就不行(不过这没关系,因为我之前说过,你根本不应该使用它们)。
而且,MessageDialog.run在一个线程中是无法工作的,所以你根本没有这个选项。这样做会导致不可预测的行为,可能会让你的应用程序表现得很奇怪,甚至崩溃。GTK对线程的支持很好,但有些事情是你绝对不应该在线程中做的,因为这样没有意义,这就是其中之一。
如果你处理的代码不需要进行任何处理,只是想等待某些事情发生(比如等待用户在对话框上按下按钮),你应该使用返回Deferred
的函数,而不是线程。实际上,gtk.Dialog
会在用户响应时发出一个信号:“response
”。你可以利用这个信号来连接一个非常简单的函数,显示你的消息对话框,并在完成时返回一个Deferred
。下面是一个例子:
def showMessage(text):
mdlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO,
buttons=gtk.BUTTONS_CLOSE,
message_format=text)
result = Deferred()
def response(dialog, response_id):
mdlg.destroy()
result.callback(response_id)
return False
mdlg.connect("response", response)
mdlg.show_all()
return result