【问题标题】:twisted + gtk: should I run GUI things in threads, or in the reactor thread?twisted + gtk:我应该在线程中还是在反应器线程中运行 GUI 东西?
【发布时间】:2023-09-11 05:21:01
【问题描述】:

根据我对扭曲的理解,反应器线程中运行的任何内容都不应阻塞。所有阻塞活动都应该委托给其他线程,以便在完成后将回调触发回反应器线程。

那么这也适用于 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)

?

编辑:哦,好吧,后两者真的搞砸了。所以我想答案是第一个。那么我的问题是:为什么?看起来这会阻塞反应器线程。

【问题讨论】:

    标签: python multithreading gtk twisted pygtk


    【解决方案1】:

    您的问题实际上与线程和 GUI 无关。您应该始终在同一个线程中使用 Twisted 和 GTK:没有必要这样做。

    您的问题是您使用的是gtk.Dialog.run()。这是一个你永远不应该使用的 API,无论是否 Twisted。它运行一个可重入的主循环,这会导致您当前的事件处理程序阻塞,但允许其他事件处理程序执行堆栈的下一层。 GTK 对可重入主循环有很好的支持,但 Twisted 没有(这没关系,因为就像我说的,你不应该使用它们)。

    MessageDialog.run 无论如何都不会在线程中工作,因此您实际上没有该选项。它会导致不可预知的行为,从而导致您的应用程序行为异常或崩溃。 GTK 对线程有很好的支持,但有些事情你永远不应该用线程做,因为它没有任何意义,这就是其中之一。

    如果您正在处理的代码不进行任何处理,而只是想等待某事发生(例如等待用户按下对话框上的按钮),您应该使用返回 Deferred 的函数s,不是线程。碰巧的是,gtk.Dialogs 将在它们被响应的地方发出一个信号:“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
    

    【讨论】:

    • 啊,这更有意义。我预感到它与run() 有关。延期肯定是要走的路。
    【解决方案2】:

    根据我使用 Gtk+ 的经验,最好的选择是在单独的线程中运行 GUI。您可以通过在 Gtk+ 主循环中运行函数(通过 idle_add 函数)与 GUI 线程通信。我不知道反应堆,但从你的例子看来,从 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 函数,并且不会阻塞反应器线程。

    这将在单独的线程中启动 Gtk+:

    import threading
    import pygtk
    pygtk.require('2.0')
    import gtk
    import gobject
    def gtk_thread():
        gtk.main()
    threading.Thread(target=gtk_thread)
    

    如果您需要一些复杂的 GUI 交互,则必须使用 continuation-passing style 进行编程

    编辑:添加在单独线程中运行 Gtk+ 的示例

    【讨论】:

    • 如何在不同的线程中运行 gui?我刚刚做了gtk2reactor.install(),并使用了那个反应器。当我尝试在没有它的情况下使用 twisted+gtk 时遇到了很大的问题。
    • 我已编辑消息以包含在单独线程中启动 Gtk+ 的代码
    【解决方案3】:

    虽然不推荐也不支持,但对于 Twisted 10.x,似乎以下代码可以继续使用 gtk.main() / dialog.run()

      gobject.idle_add(lambda *x: reactor.runUntilCurrent())
      reactor.startRunning()    
      dialog.run()
    

    【讨论】:

      最近更新 更多