How to do background task in gtk3-python?

use*_*611 5 python gtk user-interface multithreading

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()
Run Code Online (Sandbox Code Playgroud)

This code will generate this gui: img

我有第二个模块,它将执行逻辑。

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
Run Code Online (Sandbox Code Playgroud)

当我执行此操作时,该操作有效,但是gui被冻结。怎么做好呢?

编辑:

如user4815162342所说,我将代码更改为此:

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()
Run Code Online (Sandbox Code Playgroud)

使用该代码,程序有时可以工作,但有时会出现此错误。

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)
Run Code Online (Sandbox Code Playgroud)

2。

*** glibc detected *** python: free(): invalid next size (fast): 0x09c9f820 ***
Run Code Online (Sandbox Code Playgroud)

3。

Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)

use*_*342 5

您实际上并没有启动线程,只是实例化了可用于启动该线程的对象。完整的解决方案需要您的GUI线程和辅助线程之间的职责分离。您要执行的操作如下:

  1. 在单独的线程中进行繁重的计算,该线程由GUI代码生成并加入。该计算不应产生自己的线程,也不需要知道线程(当然,除了线程安全之外)。

  2. 线程完成后,使用gobject.idle_add()告诉GUI进度指示器可以撤回。(gobject.idle_add是唯一可以安全地从另一个线程调用的GTK函数。)

通过这种设置,无论计算做什么,GUI都将保持完全响应并更新进度条,并且保证GUI线程会在计算完成时注意到。关于当前代码的另外两点:

  • 实例化threading.Thread而不是继承。这样,您就不必为实现而烦恼run()。在这两种情况下,您都必须调用thread.start()来启动线程。

  • 除非您真的知道自己在做什么,否则不要致电threads_enter()和。只要记住,只要您从单个线程(初始化GTK所在的线程)中调用所有GTK函数,就可以了。threads_leave()

这是实现以上建议的概念验证代码:

    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')
Run Code Online (Sandbox Code Playgroud)