Threads on PyGTK

Any GUI developer has faced with the problem of the progress bar GUI actualization, for example, developing a download dialog. So I’ll explain how to create multithread applications with pygtk showing a simple example.

First we’ll write the basic code, we need the window, the progress bar and the exit callback that will be used to destroy the threads. The position of the progress bar is set with the set_fraction() method, which argument is a float between 0.0 to 1.0.

Example 1


import gtk

def exit(obj): """Exit callback""" gtk.main_quit() #GUI Bootstrap window = gtk.Window() progressbar = gtk.ProgressBar() window.add(progressbar) window.connect('destroy', exit) progressbar.set_fraction(0.5) #Showing the widget and its child's window.show_all() gtk.main()

Now let’s take a look on how threads works in python using the threading module. Threads are a way for a program to split itself into two or more simultaneously running tasks. The way that we create threads in python is writing subclasses of the threading.Thread class, the method run, should be overloaded so we include there the code that we want to be rant on each thread.

In order to create a new thread, we should create a new Thread object and then, call the start method which creates the new thread, and executes the run method in its context. The following example creates a Thread class that prints a string to the output.

Example 2


import threading

class FractionSetter(threading.Thread): def run(self): """This is the code executed when start method is called""" print "I'm a fancy thread" #The start method creates the new thread, then #the run method is called inside this thread #Creating three threads FractionSetter().start() FractionSetter().start() FractionSetter().start()

If we run this script we get this:

[arc@centauri:~/threaded_pygtk]$ python threaded_gtk_2.py
I’m a fancy thread
I’m a fancy thread
I’m a fancy thread

As you can see, the thread ends when the run method, but what if we want it to be persistent, we need to include a loop, and then, we need to add some signaling so we can stop the thread when we want, the appropriate tool to achieve this are threading.Event objects. threading.Event has two states, set and not set, by default, the event is not set. We can check the state of the event on every iteration of the run’s while loop. We also need a method that sets the Event on so we stop the loop and the thread.

In the following example we create a thread and wait a few seconds using the time.sleep function, then we call the stop method to set the event.

Example 3


import threading

class FractionSetter(threading.Thread): stopthread = threading.Event() def run(self): """while sentence will continue until the stopthread event is set""" while not self.stopthread.isSet(): print "I'm a fancy thread, yay!" def stop(self): self.stopthread.set() fs = FractionSetter() fs.start() #Waiting 2 seconds until the thread stop import time
time.sleep(2) #Stopping the thread fs.stop()


This is the result of the example:

[arc@centauri:~/threaded_pygtk]$ python threaded_gtk_3.py
I’m a fancy thread, yay!
I’m a fancy thread, yay!
I’m a fancy thread, yay!
[…]
I’m a fancy thread, yay!
I’m a fancy thread, yay!
[arc@centauri:~/threaded_pygtk]$

Now that we understand how threads works, let’s focus on our main problem, the progress bar. The simplest way to implement a thread that actualizes the progress bar fraction is this:


while not self.stopthread.isSet():
#Setting a random value for the fraction
progressbar.set_fraction(random.random())
#Delaying 100ms until the next iteration
time.sleep(0.1)

The problem with this, is that the gtk.main() loop won’t refresh the screen each time that the fraction is set, we need to use the gtk thread engine. We need to call the gtk.thread_init() function to start the thread engine, this call should be made on the same thread that the gtk.main() function is called. In fact, calling it right afer importing the gtk module is a good practice.

Then, we should use gtk.threads_enter() before any access to gtk object, and, once we end up doing transformations, we should call gtk.threads_leave(), the following example shows how to do it using a random fraction for the progress bar:


#While the stopthread event isn't set, the thread keeps going on
while not self.stopthread.isSet() :
# Acquiring the gtk global mutex
gtk.threads_enter()
#Setting a random value for the fraction
progressbar.set_fraction(random.random())
# Releasing the gtk global mutex
gtk.threads_leave()
#Delaying 100ms until the next iteration
time.sleep(0.1)

We should call to the stop method in the exit callback so when the window is closed the application ends the thread and the script doesn’t hangs.

The final source code looks like this:

Final Example


import threading
import random, time
import gtk
#Initializing the gtk's thread engine gtk.threads_init() class FractionSetter(threading.Thread): """This class sets the fraction of the progressbar""" #Thread event, stops the thread if it is set. stopthread = threading.Event() def run(self): """Run method, this is the code that runs while thread is alive.""" #Importing the progressbar widget from the global scope global progressbar

#While the stopthread event isn't setted, the thread keeps going on while not self.stopthread.isSet() : # Acquiring the gtk global mutex gtk.threads_enter() #Setting a random value for the fraction progressbar.set_fraction(random.random()) # Releasing the gtk global mutex gtk.threads_leave() #Delaying 100ms until the next iteration time.sleep(0.1) def stop(self): """Stop method, sets the event to terminate the thread's main loop""" self.stopthread.set() def main_quit(obj): """main_quit function, it stops the thread and the gtk's main loop""" #Importing the fs object from the global scope global fs
#Stopping the thread and the gtk's main loop fs.stop() gtk.main_quit() #Gui bootstrap: window and progressbar window = gtk.Window() progressbar = gtk.ProgressBar() window.add(progressbar) window.show_all() #Connecting the 'destroy' event to the main_quit function window.connect('destroy', main_quit) #Creating and starting the thread fs = FractionSetter() fs.start() gtk.main()

Advertisements

17 thoughts on “Threads on PyGTK

  1. Hi!
    Thanks for the tutorial – I found it very useful!
    Why should an Event object be used instead of an variable for the while loop?
    Thanks again…

    Like

  2. Useful thanks.
    Anyone could explain me why I execute the exaple 3 and I obtain a lot of “I’m a fancy thread, yay!” more than two seconds?

    Like

  3. For this example to work under windows without exceptions i had to replace gtk.threads_init() with gtk.gdk.threads_init() and wrap gtk.main in a separate thread:
    gtk.gdk.threads_enter()
    gtk.main()
    gtk.gdk.threads_leave()

    Like

  4. In my tests the thread does not stop correctly when main_quit is called, so the cleaning process does not take place. You can check it inserting a debug message after the while, it won’t be printed if you close the window.
    I managed to make it work adding a threads_leave and a thread join:
    #Stopping the thread
    gtk.gdk.threads_leave() fs.stop()
    fs.join()
    gtk.main_quit()

    Like

  5. Well written, I’ve been trying to find a usable into to pygtk threading for about an hour, thanks!
    BTW Boxed, the errors are not windows specific, but because you’re using a more recent python, and they’ve changed the structure a little. Still, good job it tells you how to fix it when you run it.

    Like

  6. Could you explain the difference between surrounding a statement with gtk.gdk.threads_enter/leave(), and using gobject.idle_add?

    Like

  7. I try it for a long time, and your great code show my problem: in my code, gtk.main() is called before the thread for progressbar is created, because is created only if is needed. SO, I must create all threads before ?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s