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
<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.
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.
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!