Python GTK 3.0:在次要线程中访问GUI是否不被支持?

3
我基于Python Gtk3开发了一个GUI程序。在主窗口中有一个按钮和一个进度条,我的目的是当按下按钮时,另一个线程会运行以完成一些工作,最终另一个GTK窗口会显示结果,同时进度条可以正确地更新主窗口中的进度。但是我总是遇到以下错误:
[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python: xcb_io.c:274: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed.
Aborted (core dumped)

我真的不知道出了什么问题,也没有任何想法,有人能帮忙看一下并提供一个例子吗?

非常感谢!

我在这里开发了一个测试程序,但在Linux平台下总是遇到以下错误:

(fileChooser.py: 40834):Gtk-CRITICAL **:gtk_text_attributes_ref:断言'values!= NULL'失败

(fileChooser.py: 40834):Gtk-CRITICAL **:gtk_text_attributes_ref:断言'values!= NULL'失败

(fileChooser.py: 40834):Pango-CRITICAL **:pango_layout_new:断言'context!= NULL'失败 分段错误(核心已转储)

#!/usr/bin/env python

import os
import re
import multiprocessing
import threading
import platform
from gi.repository import Gtk, GLib, Gdk, GObject

class ANA(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)                                                                                                                                                              
        self.tree_store = dict()
        self.COL_NAME = 0 
        self.COL_COLOR = 1 

        self.window = Gtk.Window()
        self.window.set_title("Analysing Results")
        self.window.set_size_request(1000, 750)
        self.connect('destroy', lambda *w: Gtk.main_quit())

        self.main_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1)
        self.window.add(self.main_vbox)

        self.hpaned = Gtk.HPaned()
        self.hpaned.set_position(295)
        self.main_vbox.pack_start(self.hpaned, True, True, 0)

        self.notebook = Gtk.Notebook()
        for tab in ['A', 'B']:
            scrolled_window = Gtk.ScrolledWindow()
            scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
            scrolled_window.set_shadow_type(Gtk.ShadowType.IN)

            self._new_notebook_page(self.notebook, scrolled_window, tab)

            treeview = self.__create_treeview(tab)
            scrolled_window.add(treeview)

        self.hpaned.add(self.notebook)

        self.notebook1 = Gtk.Notebook()
        self.hpaned.add(self.notebook1)
    
        self.scrolled_window2, self.info_buffer, self.info_text_view = self.__create_text(True)
        self._new_notebook_page(self.notebook1, self.scrolled_window2, '_Info')

        info_text_view = self.info_text_view

        self.window.show_all()

    def _new_notebook_page(self, notebook, widget, label):
        l = Gtk.Label(label='')
        l.set_text_with_mnemonic(label)
        notebook.append_page(widget, l)

    def __create_text(self, is_source=False):
        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        scrolled_window.set_shadow_type(Gtk.ShadowType.IN)

        text_view = Gtk.TextView()
        scrolled_window.add(text_view)

        buffer = Gtk.TextBuffer()
        text_view.set_buffer(buffer)
        text_view.set_editable(False)
        text_view.set_cursor_visible(True)

        return scrolled_window, buffer, text_view
        
    def __create_treeview(self, tab_name):
        treestore = Gtk.TreeStore(str, str)
        self.tree_store[tab_name] = treestore
        treeview = Gtk.TreeView(treestore)
        selection = treeview.get_selection()
        selection.set_mode(Gtk.SelectionMode.BROWSE)
        treeview.set_size_request(200, -1) 

        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn(tab_name, cell, foreground=self.COL_COLOR)
        column.add_attribute(cell, "text", self.COL_NAME)

        treeview.append_column(column)

        if tab_name == "A":
            selection.connect('changed', self.selection_changed_A)
        elif tab_name == "B":
            selection.connect('changed', self.selection_changed_B)

        treeview.expand_all()
        return treeview

    def selection_changed_A(self):
        print "A" 

    def selection_changed_B(self):
        print "B" 

class ANALYSING_PROCESS(multiprocessing.Process):
    def __init__(self):
        super(ANALYSING_PROCESS, self).__init__()

    def run(self):
        import time
        time.sleep(5)
        ANA()

class ANALYSING_THREAD(threading.Thread):
    def __init__(self, pbar, timer):
        super(ANALYSING_THREAD, self).__init__()
        self.pbar = pbar
        self.timer = timer

    def run(self):
        Gdk.threads_init()
        Gdk.threads_enter()
        import time
        time.sleep(5)
        ANA()
        Gdk.threads_leave()

        self.pbar.set_text("Done")
        self.pbar.set_show_text(True)
        GObject.source_remove(self.timer)

class File_Chooser(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect('destroy', lambda *w: Gtk.main_quit())

        self.set_title("Test")
        self.set_border_width(8)

        frame = Gtk.Frame()
        self.add(frame)

        self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        self.vbox.set_border_width(8)
        frame.add(self.vbox)

        label = Gtk.Label()
        label.set_markup("<span font_desc=\"Serif 25\" foreground=\"#015F85\" size=\"x-large\"> Test</span>")
        self.vbox.pack_start(label, False, False, 0)

        self.button_entry = dict()
        self.entry_name = dict()

        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)

        button = Gtk.Button("Browse Files")
        button.connect('clicked', self.browse_files)
        hbox.pack_start(button, False, False, 0)

        label = Gtk.Label()
        label.set_markup("<span foreground=\"#015F85\"> files </span>")
        label.set_use_underline(True)
        hbox.pack_start(label, False, False, 0)

        self.entry1 = Gtk.Entry()
        hbox.pack_start(self.entry1, True, True, 0)
        label.set_mnemonic_widget(self.entry1)
        self.button_entry[button] = self.entry1
        self.entry_name['files'] = self.entry1

        self.vbox.pack_start(hbox, False, False, 0)

        separator = Gtk.HSeparator()
        self.vbox.pack_start(separator, False, False, 1)

        alignment1 = Gtk.Alignment()
        alignment1.set_halign(Gtk.Align.CENTER)
        self.vbox.pack_start(alignment1, False, False, 1)

        self.pbar = Gtk.ProgressBar()
        alignment1.add(self.pbar)
        self.pbar.set_text("Not Run")
        self.pbar.set_show_text(True)


        hbox2 = Gtk.HBox(False, 2)
        self.vbox.pack_start(hbox2, False, False, 1)

        button3 = Gtk.Button("Analyze")
        button3.connect('clicked', self.tar_File_analyze)
        hbox2.pack_end(button3, False, False, 1)

        button4 = Gtk.Button("close")
        button4.connect('clicked', self.__quit)
        hbox2.pack_end(button4, False, False, 1)

        self.show_all()

    def browse_files(self, button):
        dialog = Gtk.FileChooserDialog("Please choose a file", self,
            Gtk.FileChooserAction.OPEN,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
             Gtk.STOCK_OPEN, Gtk.ResponseType.OK))

        filter = Gtk.FileFilter()
        filter.set_name("Tar files")
        filter.add_pattern("*.tar")
        filter.add_pattern("*.rar")
        filter.add_pattern("*.tar.gz")
        dialog.add_filter(filter)

        filter = Gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)

        filter = Gtk.FileFilter()
        filter.set_name("Images")
        filter.add_mime_type("image/png")
        filter.add_mime_type("image/jpeg")
        filter.add_mime_type("image/gif")
        filter.add_pattern("*.png")
        filter.add_pattern("*.jpg")
        filter.add_pattern("*.gif")
        filter.add_pattern("*.tif")
        filter.add_pattern("*.xpm")
        dialog.add_filter(filter)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            print dialog.get_filename(), 'selected'
            self.button_entry[button].set_text(dialog.get_filename())
            #entry.set_text(dialog.get_filename())
        elif response == Gtk.ResponseType.CANCEL:
            print 'Closed, no files selected'
        dialog.destroy()

    def tar_File_analyze(self, button):
            
        #PROGRESS_THREAD(self.pbar).start()
        self.pbar.set_text("Running")
        self.pbar.set_show_text(True)
        ####self.timer = GObject.timeout_add (100, self.progress_timeout, self)
        self.timer = GLib.timeout_add(100, self.progress_timeout)
            
        t = ANALYSING_THREAD(self.pbar, self.timer)
        #t = ANALYSING_PROCESS()
        t.start()

    def progress_timeout(self):
        self.pbar.pulse()
        return True

    def warning(self, warnings):
        dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.WARNING,
                Gtk.ButtonsType.OK, "Warning!")
        dialog.format_secondary_text(warnings)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            dialog.destroy()

    def __quit(self, button):
        self.destroy()

    def run(self):
        Gtk.main()

def main():
    File_Chooser().run()

if __name__ == '__main__':
    main()                                                            

有人能看一下吗?这是关于IT技术的问题。

请包含产生错误的示例代码。 - Dan D.
你看过这里吗:https://wiki.gnome.org/Projects/PyGObject/Threading?这描述了从另一个线程访问主循环的规范方式。话虽如此,我自己在使用Gtk和多线程时遇到了问题,因为我错过了某个idle_add,但我从未遇到过这样的xcb失败。所以,不确定它是否与Gtk有关,也许是代码的另一部分。 - Cilyan
我之前检查过了,但还是不起作用。 - bluezd
@DanD。我添加了示例代码,你能帮忙看一下吗?谢谢! - bluezd
@DanD. 这里有什么意见吗? - bluezd
1个回答

3

一般来说,GUI工具包不支持多线程进行工具包调用。通常只有初始线程被允许这样做。使GUI工具包真正线程安全是一个难题,这是一种避免的方式。

一种可能的解决方案是让主线程使用空闲函数或定时器回调来检查工作线程中的进度。

另一种解决方案是将工作放在外部进程而不是线程中进行。这有许多优点:

  • 它使通信明确且可追踪。GUI工具包通常可以监视文件描述符或套接字,通过超时或空闲回调处理其中的信息。
  • 保持GUI单线程使锁定大部分不必要。
  • 在CPython上,它实际上可以利用多核机器,从而规避GIL。

谢谢您的回复,我尝试将工作放在外部进程中而不是线程中,但总是会出现Gdk警告并失败,在gtk2中它可以正常工作,但在gtk3中却不能。您有任何相关示例吗? - bluezd
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - bluezd
我还没有用gtk+3编写过程序。 - Roland Smith
我在gtk 3中添加了示例。@Roland Smith - bluezd
这里有任何评论吗?@Roland Smith - bluezd
尝试在Python调试器下运行程序,以查看错误发生的位置。根据猜测,我会说错误发生在__create_text中,因为错误涉及文本属性。 - Roland Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接