Synchronizing threads in GTK3 + and Python

I am currently learning Python and GTK 3+ and have a problem synchronizing threads. I'll try to be quick and clear:

I have to make a social network client. Since the goal is to learn how to create a GUI, it "simulates access to the social network API", but I have to "wait" for network responses with time.sleep (). Calling time.sleep () on the main thread freezes the GUI (it stops Gtk.Main () from executing), so I need to make all my connections on a separate thread. And here's my problem. When I authenticate the user (verification_credentials), I need to wait until the end of this thread to continue executing the main program. If I try to freeze Thread.join GUI. I tried to use queues, but queue.get also blocks Gtk.main (). I tried to fix the signal when my thread is finished, but the handler runs on the same thread, so,when I try to change the GUI (which I need) the program crashes (you shouldn't touch the GUI from anywhere other than the main thread).

My decision? I am in the business of revitalizing / active waiting, which is anti-pattern by definition. I keep asking if the thread ended and forcing Gtk.main () loops

There must be another way, more elegant / efficient than mine. I don't know if I can signal another thread, or if there is a way to use queues without blocking the main thread. Any help would be much appreciated.

Python code:

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 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()

      

The glade file is in pastebin because it will exceed the post size limit.

http://pastebin.com/8S3k7f6J

Thanks in advance for any help you could provide.

+3


source to share


1 answer


You can use GLib.idle_add

to schedule a callback to be executed by an event loop on the main thread of your program. This means that it provides a safe way to schedule a GUI update from a background thread. Thus, you can simply let the background thread run normally, let the main thread return control to the event loop, and then make the appropriate GUI updates from the background thread GLib.idle_add

after it runs:

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!")

      



You are actually very close to using this same method, you just make it awkward - you plan to active_waiting

run on a main thread that waits until the background thread is done and then calls z_handler

. Scheduling z_handler

directly after the background thread is running with it running is much easier.

+4


source







All Articles