Threading in Gtk python

10,659

Solution 1

So I finally managed to get it to work. I needed to say:

from gi.repository import Gtk,GObject

GObject.threads_init()
Class Gui:
    .....
    ......
    def on_update_click():
            Thread(target=update).start()

At first I used:

thread.start_new_thread(update())

in the on_update_click function. As mentioned my J.F Sebastian this was incorrect as this would immediately call this thread. This froze my whole computer.

I then just added:

Thread(target=update).start()

The on_update_clicked function only worked once the main Thread Gtk.main() was closed. So the threads were not running simultaneously.

by adding: GObject.threads_init()

this allowed for the threads to run serially to the python interpreter: Threads in Gtk!

Solution 2

thread.start_new_thread(update()) is wrong. It calls update() immediately in the main thread and you shouldn't use thread module directly; use threading module instead.

You could call threading.current_thread() to find out which thread executes update().

To simplify your code you could run all gtk code in the main thread and use blocking operations to retrieve web-pages and run them in background threads.

Based on the extended example from GTK+ 3 tutorial:

#!/usr/bin/python
import threading
import urllib2
from Queue import Queue

from gi.repository import Gtk, GObject

UPDATE_TIMEOUT = .1 # in seconds

_lock = threading.Lock()
def info(*args):
    with _lock:
        print("%s %s" % (threading.current_thread(), " ".join(map(str, args))))

class MyWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")

        self.button = Gtk.Button(label="Click Here")
        self.button.connect("clicked", self.on_button_clicked)
        self.add(self.button)

        self.updater = Updater()
        self._update_id = None
        self.update()

    def on_button_clicked(self, widget):
        info('button_clicked')
        self.update()

    def update(self):
        if self._update_id is not None: 
            GObject.source_remove(self._update_id)

        self.updater.add_update(self.done_updating) # returns immediately
        # call in UPDATE_TIMEOUT seconds
        self._update_id = GObject.timeout_add(
            int(UPDATE_TIMEOUT*1000), self.update)

    def done_updating(self, task_id):
        info('done updating', task_id)
        self.button.set_label("done updating %s" % task_id)


class Updater:
    def __init__(self):
        self._task_id = 0
        self._queue = Queue(maxsize=100) #NOTE: GUI blocks if queue is full
        for _ in range(9):
            t = threading.Thread(target=self._work)
            t.daemon = True
            t.start()

    def _work(self):
        # executed in background thread
        opener = urllib2.build_opener()
        for task_id, done, args in iter(self._queue.get, None):
            info('received task', task_id)
            try: # do something blocking e.g., urlopen()
                data = opener.open('http://localhost:5001').read()
            except IOError:
                pass # ignore errors

            # signal task completion; run done() in the main thread
            GObject.idle_add(done, *((task_id,) + args))

    def add_update(self, callback, *args):
        # executed in the main thread
        self._task_id += 1
        info('sending task ', self._task_id)
        self._queue.put((self._task_id, callback, args))

GObject.threads_init() # init threads?

win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()

Gtk.main()

Note: GObject.idle_add() is the only gtk-related function that is called from different threads.

See also Multi-threaded GTK applications – Part 1: Misconceptions.

Share:
10,659
zeref
Author by

zeref

Updated on June 20, 2022

Comments

  • zeref
    zeref almost 2 years

    So I'm busy writing an application that needs to check for updates from a website after a certain amount ouf time, I'm using python with Gtk +3

    main.py file

    class Gui:
        ...
        def on_update_click():
            update()
    
    app=Gui()
    Gtk.main()
    

    update.py file

    def update():
        #check site for updates
        time.sleep(21600) #check again in 6hrs
    

    I suspect I'll have to use threading. my thinking is:

    Gtk.main() runs the main thread.

    when the user clicks the update button, update() runs in the background. #thread 2

    Is my thinking correct or have I missed something?

    EDIT: Ok,
    on_update_click function:

                Thread(target=update).start(). 
    

    K, computer does not freeze anymore :D

    so what happens now is that only when I close Gtk.main() does the update thread only start. It's good that is continues to update when the UI is closed, but i'd also like it to start when the UI is up.

    • jfs
      jfs over 11 years
      on_update_click() misses self argument.
  • zeref
    zeref over 11 years
    I dont understand what you mean by: Run long-running blocking function
  • Rostyslav Dzinko
    Rostyslav Dzinko over 11 years
    Connecting to site and fetching page contents can be a long-running operation - up to several seconds. If you make it with e.g. urllib, your GUI (the app is single-threaded for now) will freeze for the time update function is performing. That means blocking: GUI event handlers can't be executed until update is done. So that's why I advice either run update() in thread or use asynchronous (non-blocking) network operations.
  • zeref
    zeref over 11 years
    Ok, so in the on_upate_click function added