【问题标题】:Thread synchronization in GTK3+ and PythonGTK3+ 和 Python 中的线程同步
【发布时间】:2014-12-15 21:04:23
【问题描述】:

我目前正在学习 Python 和 GTK 3+,但在同步线程时遇到了问题。我会尽量简洁明了:

我必须创建一个社交网络客户端。由于目的是学习如何创建 GUI,因此将模拟“对社交网络 API 的访问”,但我必须使用 time.sleep()“等待”网络响应。在主线程中调用 time.sleep() 会冻结 GUI(它会停止执行 Gtk.Main()),所以我必须在单独的线程中建立所有连接。 这就是我的问题。当我验证用户(verifying_credentials)时,我需要等待该线程完成以继续执行主程序。如果我尝试 Thread.join GUI 冻结。我尝试过使用队列,但 queue.get 也阻塞了 Gtk.main()。 我尝试在线程完成时发出信号,但处理程序在同一个线程中启动,所以当我尝试修改 GUI(我需要)时,程序崩溃(你不应该从任何地方触摸 GUI除了主线程)。

我的解决方案?我做 busy-waiting / active-waiting ,根据定义,这是一种反模式。我一直在问线程是否已经完成并强制 Gtk.main() 循环

必须有另一种方式,一种比我的更优雅/更有效的方式。 我不知道我是否可以向另一个线程发出信号,或者有一种方法可以在不阻塞主线程的情况下使用队列。任何帮助将不胜感激。

python 代码:

from os.path import abspath, dirname, join
import gettext
import threading
import math
import time
import random
import locale

from gi.repository import Gtk, Gdk, GLib, GObject
import list

APP = "UDC_Social_API"
user_list = {
    "user": "password",
    "admin": "admin",
    "carnotan": "1234"
}

class UDC_Social_API:

    def on_applicationwindow1_key_press_event(self, widget, event):
        # keyname = Gdk.keyval_name(event.keyval)
        # print (_("Key %(name)s (%(val)d) was pressed" )%{"name":keyname, "val":event.keyval})
        if event.keyval == 65293:
            self.on_login_button_clicked()

    def delay(self):
        if self.delay_option.get_active():
            time.sleep(math.exp(random.random()*5))
        else:
            pass

    def active_waiting(self):
        while self.finished is False:
            Gtk.main_iteration_do(False)
        self.finished = False
        self.z_handler(None)

    def verify_credentials(self, user, password):
        GLib.idle_add(self.active_waiting)
        self.delay()
        if user in user_list:
            if password == user_list.get(user):
                self.authentication = True
                self.finished = True
            else:
                self.authentication = False
                self.finished = True
        else:
            self.authentication = False
            self.finished = True

    def on_login_button_clicked(self, data=None):
        user = self.user_entry.get_text()
        password = self.password_entry.get_text()
        thread = threading.Thread(target=self.verify_credentials, args=(user, password))
        thread.daemon = True
        thread.start()

    def z_handler(self, data=None):
        if self.authentication is False:
            self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
            self.message_dialog.run()
            self.message_dialog.hide()
            return False
        else:
            self.window.hide()
            print ("Success!")



    def on_applicationwindow1_destroy(self, data=None):
        Gtk.main_quit()

    def on_gtk_about_activate(self, menuitem, data=None):
        self.aboutdialog.run()
        self.aboutdialog.hide()

    def on_gtk_cut_activate(self, widget):
        # Get the bounds of the selected text
        bounds = self.focus.get_selection_bounds()
        # if the bounds of the selection are not an empty tuple,
        # put the selection in the variable chars
        # and copy it to the clipboard
        # (get_selection_bounds returns an empty tuple if there is no selection)
        # then delete the selection
        if bounds:
            chars = self.focus.get_chars(*bounds)
            self.clipboard.set_text(chars, -1)
            self.focus.delete_text(bounds[0], bounds[1])
        else:
            pass

    def on_gtk_copy_activate(self, widget):
        # Get the bounds of the selected text
        bounds = self.focus.get_selection_bounds()
        # if the bounds of the selection are not an empty tuple,
        # put the selection in the variable chars
        # and copy it to the clipboard
        # (get_selection_bounds returns an empty tuple if there is no selection)
        if bounds:
            chars = self.focus.get_chars(*bounds)
            self.clipboard.set_text(chars, -1)
        else:
            pass

    def on_gtk_paste_activate(self, widget):
        # Get the text from the clipboard
        text = self.clipboard.wait_for_text()
        if text is not None:
            # If there's text selected in the target
            # delete it and paste the contents of the clipboard
            bounds = self.focus.get_selection_bounds()
            if bounds:
                self.focus.delete_text(bounds[0], bounds[1])
                self.focus.insert_text(text, bounds[0])

            # else insert the text in the current position of the cursor in the target
            else:
                pos = self.focus.get_position()
                self.focus.insert_text(text, pos)
        else:
            pass

    def on_entry_focus(self, widget, event):
        self.focus = widget

    def create_menubar(self):
        self.file_menu=self.builder.get_object("menuitem1")
        self.edit_menu=self.builder.get_object("menuitem2")
        self.options_menu=self.builder.get_object("option")
        self.help_menu=self.builder.get_object("menuitem4")
        self.languages_menu=self.builder.get_object("menuitem3")
        self.delay_option = self.builder.get_object("delay_option")
        self.gtk_quit_menu=self.builder.get_object("gtk_quit_menu")
        self.gtk_cut_menu=self.builder.get_object("gtk_cut_menu")
        self.gtk_copy_menu=self.builder.get_object("gtk_copy_menu")
        self.gtk_paste_menu=self.builder.get_object("gtk_paste_menu")
        self.gtk_about_menu=self.builder.get_object("gtk_about_menu")
        self.galician_option=self.builder.get_object("radiomenuitem1")
        self.spanish_option=self.builder.get_object("radiomenuitem2")
        self.english_option=self.builder.get_object("radiomenuitem3")

    def set_menubar_names(self):
        self.file_menu.set_label(_("_File"))
        self.edit_menu.set_label(_("_Edit"))
        self.options_menu.set_label(_("_Options"))
        self.help_menu.set_label(_("_Help"))
        self.languages_menu.set_label(_("_Languages"))
        self.delay_option.set_label(_("_Delay"))
        self.gtk_quit_menu.set_label(_("Quit"))
        self.gtk_copy_menu.set_label(_("Copy"))
        self.gtk_cut_menu.set_label(_("Cut"))
        self.gtk_paste_menu.set_label(_("Paste"))
        self.gtk_about_menu.set_label(_("About"))
        self.galician_option.set_label(_("_Galician"))
        self.spanish_option.set_label(_("_Spanish"))
        self.english_option.set_label(_("_English"))

    def create_login_box(self):
        self.user_entry = self.builder.get_object("user_entry")
        self.password_entry = self.builder.get_object("password_entry")
        self.user_label=self.builder.get_object("user_label")
        self.password_label=self.builder.get_object("password_label")
        self.login_button=self.builder.get_object("login_button")

    def set_login_box_names(self):
        self.user_entry.set_placeholder_text(_("user"))
        self.password_entry.set_placeholder_text(_("password"))
        self.user_label.set_label(_("User"))
        self.password_label.set_label(_("Password"))
        self.login_button.set_label(_("Login"))

    def create_about_dialog(self):
        self.aboutdialog = self.builder.get_object("aboutdialog1")
        self.aboutdialog.set_transient_for(self.window)
    def set_about_dialog(self):
        self.aboutdialog.set_comments(_("Developed for GTK 3+ and Python 3.4"))

    def reset_names(self):
        self.set_menubar_names()
        self.set_login_box_names()

    def on_radiomenuitem1_toggled(self, widget):
        if widget.get_active():
            self.lang_gl_ES.install()
            self.reset_names()
            self.window.queue_draw()
        else:
            pass

    def on_radiomenuitem2_toggled(self, widget):
        if widget.get_active():
            self.lang_es_ES.install()
            self.reset_names()
            self.window.queue_draw()
        else:
            pass

    def on_radiomenuitem3_toggled(self,widget):
        if widget.get_active():
            self.lang_en_US.install()
            self.set_menubar_names()
            self.window.queue_draw()
        else:
            pass

    def set_languages(self):
        WHERE_AM_I = abspath(dirname(__file__))
        locale.setlocale(locale.LC_ALL, '')
        locale.bindtextdomain(APP, WHERE_AM_I)
        locale_path = WHERE_AM_I +'/'
        self.builder.set_translation_domain(APP)
        gettext.find(APP,localedir=locale_path,languages=['gl_ES'])
        gettext.find(APP,localedir=locale_path,languages=['es_ES'])
        gettext.find(APP,localedir=locale_path,languages=['en_US'])
        gettext.install(APP,locale_path)
        gettext.textdomain(APP)
        gettext.bindtextdomain(APP,locale_path)
        self.lang_gl_ES=gettext.translation(APP,localedir=locale_path, languages=['gl_ES'])
        self.lang_es_ES=gettext.translation(APP,localedir=locale_path, languages=['es_ES'])
        self.lang_en_US=gettext.translation(APP,localedir=locale_path, languages=['en_US'])

    def set_signals(self):
        handlers = {
            "on_applicationwindow1_destroy": self.on_applicationwindow1_destroy,
            "on_gtk_about_activate": self.on_gtk_about_activate,
            "on_login_button_clicked": self.on_login_button_clicked,
            "on_applicationwindow1_key_press_event": self.on_applicationwindow1_key_press_event,
            "on_entry_focus": self.on_entry_focus,
            "on_gtk_cut_activate": self.on_gtk_cut_activate,
            "on_gtk_copy_activate": self.on_gtk_copy_activate,
            "on_gtk_paste_activate": self.on_gtk_paste_activate,
            "on_radiomenuitem1_toggled": self.on_radiomenuitem1_toggled,
            "on_radiomenuitem2_toggled": self.on_radiomenuitem2_toggled,
            "on_radiomenuitem3_toggled": self.on_radiomenuitem3_toggled
        }
        self.builder.connect_signals(handlers)

    def __init__(self):
        # GObject.signal_new("z_signal", Gtk.ApplicationWindow, GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ())
        self.builder = Gtk.Builder()
        self.builder.add_from_file("p1.glade")
        self.window = self.builder.get_object("applicationwindow1")
        self.set_languages()
        self.create_menubar()
        self.create_login_box()
        self.create_about_dialog()
        self.reset_names()
        self.set_signals()
        self.focus = None
        self.finished = False
        self.authentication = False
        # self.statusbar = self.builder.get_object("statusbar1")
        # self.context_id = self.statusbar.get_context_id("status")
        # self.status_count = 0
        self.message_dialog = self.builder.get_object("messagedialog1")
        self.message_dialog.set_transient_for(self.window)
        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        self.window.show_all()

if __name__ == "__main__":
    GObject.threads_init()
    main = UDC_Social_API()
    Gtk.main()

Glade 文件在 pastebin 中,因为它会超出帖子大小限制。

http://pastebin.com/8S3k7f6J

提前感谢您提供的任何帮助。

【问题讨论】:

  • 如果您在 Google 上搜索 Python、Gtk 和线程,第一个点击应该是 this example from the wiki,它显示了如何完全按照您的意愿行事。
  • @abarnert 相信我:我已经读过了。事实上,多亏了那个页面,我的 GUI 不再每次都冻结了

标签: python multithreading user-interface gtk thread-synchronization


【解决方案1】:

您可以使用GLib.idle_add 来安排回调,以由程序主线程中的事件循环执行。这意味着它提供了一种从后台线程安排 GUI 更新的安全方法。所以,你可以让你的后台线程正常运行,让主线程将控制权返回给事件循环,然后在完成后通过GLib.idle_add从后台线程进行适当的GUI更新:

def verify_credentials(self, user, password):
    self.delay()
    if user in user_list:
        if password == user_list.get(user):
            self.authentication = True
        else:
            self.authentication = False
    else:
        self.authentication = False
    # Schedule z_handler to be called by the event loop in the main thread.
    GLib.idle_add(z_handler, None) 

def z_handler(self, data=None):
    if not self.authentication:
        self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
        self.message_dialog.run()
        self.message_dialog.hide()
        return False
    else:
        self.window.hide()
        print ("Success!")

您实际上非常接近使用相同的方法,只是以一种尴尬的方式进行操作 - 您正在调度 active_waiting 在主线程中运行,该线程一直等到后台线程完成,并且然后调用z_handler。在后台线程完成工作后直接调度z_handler,要简单得多。

【讨论】:

  • 非常感谢。你不知道我花了多少时间在这上面。我的问题是我没有完全理解 GLib.idle_add 是如何工作的。我以为你需要在阻塞操作之前。我现在明白它是如何工作的。其实很简单。非常感谢您,不仅花时间回答它,而且认为这是一个好问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-20
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-09
  • 2014-11-02
相关资源
最近更新 更多