3
votes

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.

3
on_update_click() misses self argument.jfs

3 Answers

7
votes

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!

4
votes

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.

0
votes

Threading is the first way to solve the problem. You can create thread and run long-running blocking function inside that thread (and your GUI won't hang up).

Another way is to use asynchronous networking e.g. using python-gio (GObject-IO) or another library that has a possibility to work with GLib's main loop (like they do with Twisted). This approach is a bit different and uses non-blocking socket operations. Your main loop will make a callback when data from socket (site you're polling) will be available to read. Unfortunately GIO has no high-level HTTP API, so you can use GSocketClient and manually create HTTP requests structure.