【问题标题】:How to do background task in gtk3-python?如何在 gtk3-python 中执行后台任务?
【发布时间】:2013-06-05 07:36:42
【问题描述】:

我有这个主线程:

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()

此代码将生成此 gui:

我有第二个模块来执行逻辑。

进程.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

当我执行此操作时,操作正常,但 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()

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

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)

【问题讨论】:

    标签: python multithreading user-interface gtk


    【解决方案1】:

    您实际上并没有启动线程,您只是实例化了一个可用于启动它的对象。完整的解决方案需要仔细分离 GUI 线程和工作线程之间的职责。您要做的是:

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

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

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

    • 实例化threading.Thread,而不是从它继承。这样您就不必费心实施run()。不过,在这两种情况下,您都必须调用 thread.start() 才能启动线程。

    • 不要打电话给threads_enter()threads_leave(),除非你真的知道你在做什么。请记住,只要您从单个线程(初始化 GTK 的同一线程)调用所有 GTK 函数,就可以了。

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

        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')
    

    【讨论】:

    • 谢谢,它可以工作,但有时我会出错。看看我的更新。
    • 啊,我知道我的问题。还有一些代码可以从我的线程访问 gtk ui:p
    • @user2435611 很高兴听到。这个答案可能对其他尝试相同的人有用;如何正确使用带有线程的 PyGTK 远非显而易见。另外,如果您的崩溃得到解决,请不要忘记回复您的other question 并接受它。
    • 你知道我退出窗口时如何杀死后台线程吗?
    • 我不认为 Python 支持杀死线程。线程只能自愿退出,因此您的工作线程需要不时检查“退出请求”标志。如果你需要真正的异步杀戮,你需要生成一个子进程并使用os.kill杀掉它。
    【解决方案2】:

    正如您所建议的,您需要为此启动另一个线程。通常,python 中的线程非常简单,但使用 GUI 可能会变得棘手。

    这应该会有所帮助:Python. Doing some work on background with Gtk GUI

    【讨论】:

    • 很好,我已经看到了,但我错过的是GLib.threads_init() :D 但是,由于重物不可迭代,如何使进度条跳动?
    • 当我在某些东西仍在运行时单击该按钮时,为什么会出现此错误? *** glibc detected *** python: double free or corruption (!prev): 0x09154320 *** 和另一个类似的错误 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)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-09
    • 1970-01-01
    • 2012-02-28
    • 1970-01-01
    相关资源
    最近更新 更多