5

I have this main thread:

Gui.py

from gi.repository import Gtk, Gdk
import Process
import gobject

class gui():
    def __init__(self):
        self.window = Gtk.Window()
        self.window.connect('delete-event', Gtk.main_quit)

        self.box = Gtk.Box()
        self.window.add(self.box)

        self.label = Gtk.Label('idle')
        self.box.pack_start(self.label, True, True, 0)

        self.progressbar = Gtk.ProgressBar()
        self.box.pack_start(self.progressbar, True, True, 0)

        self.button = Gtk.Button(label='Start')
        self.button.connect('clicked', self.on_button_clicked)
        self.box.pack_start(self.button, True, True, 0)

        self.window.show_all()
        gobject.threads_init()

        Gdk.threads_enter()
        Gtk.main()
        Gdk.threads_leave()

    def working1():
        self.label.set_text('working1')
        t = Process.Heavy()
        t.heavyworks1() 
        self.label.set_text('idle') 

    def on_button_clicked(self, widget):
        Gdk.threads_enter()
        working1()
        Gdk.threads_leave()

if __name__ == '__main__':
    gui = gui()

This code will generate this gui: img

and I have second modul which will do the logic.

Process.py

import threading

class Heavy(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def heavyworks1(self):
        #doing heavy works1
        #return result

   def heavyworks2(self, *param):
        #doing heavy works2
        #return result

When I execute this, the operation works, but the gui became freeze. How to do it well?

EDIT:

as user4815162342 said, I change my code to this:

from gi.repository import Gtk, Gdk, GLib
import Process
import gobject
import threading

class gui():
def __init__(self):
    self.window = Gtk.Window()
    self.window.connect('delete-event', Gtk.main_quit)

    self.box = Gtk.Box()
    self.window.add(self.box)

    self.label = Gtk.Label('idle')
    self.box.pack_start(self.label, True, True, 0)

    self.progressbar = Gtk.ProgressBar()
    self.box.pack_start(self.progressbar, True, True, 0)

    self.button = Gtk.Button(label='Start')
    self.button.connect('clicked', self.on_button_clicked)
    self.box.pack_start(self.button, True, True, 0)

    self.window.show_all()

    gobject.threads_init()
    GLib.threads_init()
    Gdk.threads_init()
    Gdk.threads_enter()
    Gtk.main()
    Gdk.threads_leave()

def init_progress(self, func, arg):
    self.label.set_text('working1')
    self.worker = threading.Thread(target=func, args=[arg])
    self.running = True
    gobject.timeout_add(200, self.update_progress)
    self.worker.start()

def update_progress(self):
    if self.running:
        self.progressbar.pulse()
    return self.running

def working(self, num):
    Process.heavyworks2(num)    
    gobject.idle_add(self.stop_progress)

def stop_progress(self):
    self.running = False
    self.worker.join()
    self.progressbar.set_fraction(0)
    self.label.set_text('idle') 

def on_button_clicked(self, widget):
    self.init_progress(self.working, 100000)

if __name__ == '__main__':
    gui = gui()

with that code, program sometimes working but sometimes getting this error.

1.

**
Gtk:ERROR:/build/buildd/gtk+3.0-3.4.2/./gtk/gtktextview.c:3726:gtk_text_view_validate_onscreen: assertion failed: (priv->onscreen_validated)
Aborted (core dumped)

2.

*** glibc detected *** python: free(): invalid next size (fast): 0x09c9f820 ***

3.

Segmentation fault (core dumped)
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
user2435611
  • 1,093
  • 1
  • 12
  • 18

2 Answers2

7

You didn't actually start the thread, you only instantiated an object that can be used to start it. A full solution requires a careful separation of responsibilities between your GUI thread and your worker thread(s). What you want to do is the following:

  1. Do your heavy calculation in the separate thread, spawned and joined by the GUI code. The calculation should not spawn its own threads, nor does it need to be aware of threads (except for being thread-safe, of course).

  2. When the thread is done, use gobject.idle_add() to tell the GUI that the progress indicator can be withdrawn. (gobject.idle_add is about the only GTK function that is safe to call from another thread.)

With such a setup, the GUI remains fully responsive and progress bar updated no matter what the calculation does, and the GUI thread is guaranteed to notice when the calculation finishes. Two additional points regarding your current code:

  • Instantiate threading.Thread instead of inheriting from it. That way you don't need to bother with implementing run(). In both cases you have to call thread.start(), though, to start off the thread.

  • Don't call threads_enter() and threads_leave(), unless you really know what you are doing. Just remember that as long as you call all your GTK functions from a single thread (the same thread in which you initialized GTK), you'll be fine.

Here is proof-of-concept code that implements the above suggestions:

    def working1(self):
        self.label.set_text('working1')
        self.work_thread = threading.Thread(self.run_thread)
        self.running = True
        gobject.timeout_add(200, self.update_progress)
        self.work_thread.start()
        # the GUI thread now returns to the mainloop

    # this will get periodically called in the GUI thread
    def update_progress(self):
        if self.running:
            self.progressbar.pulse()   # or set_fraction, etc.
        return self.running

    # this will get run in a separate thread
    def run_thread(self):
        Process.heavyworks1()      # or however you're starting your calculation
        gobject.idle_add(self.stop_progress)

    # this will get run in the GUI thread when the worker thread is done
    def stop_progress(self):
        self.running = False
        self.work_thread.join()
        self.label.set_text('idle')
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Thanks, it works but sometimes I'm getting error. Looks at my update. – user2435611 Jun 06 '13 at 15:48
  • Ah,I know my problem. There's still some code that accessing gtk ui from my thread :p – user2435611 Jun 07 '13 at 11:17
  • @user2435611 Good to hear. This answer might be useful for others who attempt the same; it's far from obvious how to use PyGTK with threads correctly. Also, if your crash is resolved, please don't forget to answer your [other question](http://stackoverflow.com/questions/16947842/segmentation-fault-in-python) and accept it. – user4815162342 Jun 07 '13 at 14:34
  • do you know how to kill the background thread when I quit my window? – user2435611 Jul 02 '13 at 12:24
  • I don't think Python supports killing threads. Threads can only exit voluntarily, so your worker thread needs to check an "exit requested" flag from time to time. If you need real asynchronous killing, you will need to spawn a subprocess and kill it using `os.kill`. – user4815162342 Jul 02 '13 at 14:42
0

As you suggested you need to start another thread for this. Usually threading in python is pretty straightforward but it can get tricky with GUIs.

This should be of help: Python. Doing some work on background with Gtk GUI

Community
  • 1
  • 1
pypat
  • 1,096
  • 1
  • 9
  • 19
  • nice, I've seen that, but what I missed is `GLib.threads_init()` :D but, how to make the progressbar pulsing since the heavyworks isn't iterable? – user2435611 Jun 05 '13 at 12:38
  • when I clicked that button while something still running, why did I get this error? `*** glibc detected *** python: double free or corruption (!prev): 0x09154320 ***` and also another error like this one `Gtk:ERROR:/build/buildd/gtk+3.0-3.4.2/./gtk/gtktextview.c:3726:gtk_text_view_validate_onscreen: assertion failed: (priv->onscreen_validated) Aborted (core dumped)` – user2435611 Jun 05 '13 at 12:40