Wednesday, April 28, 2010

Solution for multithreaded GTK+ GUI hang and Random Crash.


Here is a solution that i found for multi-threaded PyGTK+ GUI hang/Crash. After a long search i found this code in some where on the internet, but i can't remember where is it really placed.


For What:
GTK+ is not thread safe, So we have to use "gtk.gdk.threads_enter" and "gtk.gdk.threads_leave" for that. This will make threading safe on GTK+, Like this
#!/usr/bin/env python
# -*- coding: utf-8 -*-
gtk.gdk.threads_enter()
#Handling GTK+ GUI
gtk.gdk.threads_leave()

but some time GUI hangs when same thread acquire this lock  for more than one time like this.
gtk.gdk.threads_enter()
#Handling GTK+ GUI
gtk.gdk.threads_enter()  #************ 
#again Handling GTK+ GUI
gtk.gdk.threads_leave()
So we have to find is this thread has it's lock already or not and make lock.
here is the code that will track threads and find is that really has it lock or not and make correct Locks.

Code:Download
class Check_For_Lock:
        def __init__(self):
                self.lock = threading.Lock()
                self.thread = None
                self.locked = 0
        def __enter__(self):
                with self.lock: DoLock = (thread.get_ident()!=self.thread)
                if DoLock:
                        gtk.gdk.threads_enter()
                        with self.lock: self.thread = thread.get_ident()
                self.locked += 1
                return None

        def __exit__(self, exc_type, exc_value, traceback):
                with self.lock:
                        self.locked -= 1
                        if self.thread!=thread.get_ident():
                                print "!ERROR! Thread freenot locked lock!"
                                sys.exit(0)
                        else:
                                if self.locked == 0:
                                        self.thread = None
                                        #Added BY me  SEE WHY?:
                                        #http://library.gnome.org/devel/gtk-faq/stable/x491.html
                                        gtk.gdk.flush()
                                        gtk.gdk.threads_leave()
                return None
TLocker = Check_For_Lock()

def TLocked(f):
    def wraps(f):return f
    #@wraps(f)
    def wrapper(*args, **kwds):
        with TLocker.lock:
            if TLocker.thread == None or TLocker.thread==thread.get_ident():
                TLocker.thread = thread.get_ident()
                TLocker.locked += 1
                WeHold = True
            else:
                print "***ERROR: GtkLocked for non-owned thread!"
                WeHold = False
        ret = f(*args, **kwds)
        if WeHold:
            with TLocker.lock:
                TLocker.locked -= 1
                if TLocker.locked == 0: TLocker.thread = None
        return ret
    return wrappr
I used this code for my project RoxBird Download Manager.
I got so many crashes on RoxBird. after that i removed gdk.threads_enter()/leave() and used this, Now RoxBird have a stable GUI.

you can use it simply like this

with TLocker:
    #Make changes on GTK+ GUI.
    window.set_size_request(400, 300)
#Connecting Signal via threads
widget.connect('signal_name', TLocked(CallFunction), UsrArg1, UsrArg2, ...)


Notes: 
  • You should create "gtk.gdk.threads_init()" after importing "gtk" module.  
  • You should use only one Instance of TLocker for the all application. (you can use reference for TLocker)
  • In threads all GUI handling should be inside of the "with TLocker" block.
  • "widget.connect" should called with TLocked like above, and connect statement should not inside of the "with TLocker" 
if i miss something on code or notes, feel free to comment.

No comments:

Post a Comment