Chickenmarkus
Chickenmarkus

Reputation: 1161

Dialog in thread freeze whole app despite Gdk.threads_enter/leave()

I've got an application which makes something in background. To inform the user it update some widgets with its progress. That works.

But somethings there is an error or something else in this background operation so it has to display a dialog. This freeze my whole application although I handle everything with the Threading-Lock. An example of code with exactly my problem is this:

import threading, time
from gi.repository import Gtk, Gdk

def background(label, parent):
    for t in range(5):
        label.set_text(str(t))
        time.sleep(1)
    Gdk.threads_enter()
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")
    dlg.run()
    dlg.destroy()
    Gdk.threads_leave()

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    Gdk.threads_enter()
    thread.start()
    Gtk.main()
    Gdk.threads_leave()

if __name__=="__main__":
    main()

Upvotes: 0

Views: 529

Answers (1)

Aran-Fey
Aran-Fey

Reputation: 43166

In gtk3, all gtk functions like adding/removing/changing widgets must be executed by the gtk thread (the thread that's running Gtk.main()).

The fixed code:

import threading, time
from gi.repository import Gtk, Gdk, GLib # need GLib for GLib.PRIORITY_DEFAULT

# a short utility function that I like to use.
# makes the Gtk thread execute the given callback.
def add_mainloop_task(callback, *args):
    def cb(args):
        args[0](*args[1:])
        return False
    args= [callback]+list(args)
    Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT, cb, args)

def background(label, parent):
    for t in range(5):
        #~ label.set_text(str(t)) # let the gtk thread do this.
        add_mainloop_task(label.set_text, str(t))
        time.sleep(1)
    #~ Gdk.threads_enter() # don't need this.
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")

    # put these two functions calls inside a little function, and let the gtk thread execute it.
    def run_dialog(dlg):
        dlg.run()
        dlg.destroy()
    add_mainloop_task(run_dialog, dlg)
    #~ Gdk.threads_leave() # don't need this.

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    #~ Gdk.threads_enter() # don't need this.
    thread.start()
    Gtk.main()
    #~ Gdk.threads_leave() # don't need this.

if __name__=="__main__":
    main()

Upvotes: 3

Related Questions