简体   繁体   English

在 Python GTK 应用程序 (PyGObject) 中实现线程以防止 UI 冻结

[英]Implementing threading in a Python GTK application (PyGObject) to prevent UI freezing

Simply put, I want to properly implement threading in a Python GTK application.简单地说,我想在 Python GTK 应用程序中正确实现线程。 This is in order to prevent UI freezing due to functions/code taking a long time to finish running.这是为了防止由于功能/代码需要很长时间才能完成运行而导致 UI 冻结。 Hence, my approach was to move all code which took a long time to run into separate functions, and run them in their separate threads as needed.因此,我的方法是将所有需要很长时间才能运行的代码移到单独的函数中,并根据需要在各自的线程中运行它们。 This however posed a problem when trying to run the functions in sequence.然而,当尝试按顺序运行这些函数时,这会带来一个问题。
For example, take a look at the following code:例如,看看下面的代码:

class Main(Gtk.Window):
    def __init__(self):
        super().__init__()

        self.button = Gtk.Button(label='button')
        self.add(self.button)
        self.button.connect('clicked', self.main_function)


    def threaded_function(self):
        time.sleep(20)
        print('this is a threaded function')


    def first_normal_function(self):
        print('this is a normal function')


    def second_normal_function(self):
        print('this is a normal function')


    def main_function(self, widget):
        self.first_normal_function()
        self.threaded_function()
        self.second_normal_function()

Pressing the button starts main_function which then starts 3 functions in sequence.按下按钮启动main_function ,然后依次启动 3 个函数。 threaded_function represents a function which would take a long time to complete. threaded_function代表一个 function 需要很长时间才能完成。 Running this as is will freeze the UI.按原样运行会冻结 UI。 Hence it should be threaded as such:因此,它应该这样穿线:

...
...

def main_function(self, widget):
    self.first_normal_function()
    
    thread = threading.Thread(target=self.threaded_function)
    thread.daemon = True
    thread.start()
    
    self.second_normal_function()

What should happen is that the following first_normal_function should run, then threaded_function in a background thread - the UI should remain responsive as the background thread is working.应该发生的是以下first_normal_function应该运行,然后threaded_function在后台线程中运行 - UI 应该在后台线程工作时保持响应。 Finally, second_normal_function should run, but only when threaded_function is finished.最后, second_normal_function应该运行,但仅在threaded_function完成时运行。

The issue with this is that the functions will not run in sequence.这样做的问题是这些功能不会按顺序运行。 The behaviour I am looking for could be achieved by using thread.join() however this freezes the UI.我正在寻找的行为可以通过使用thread.join()来实现,但这会冻结 UI。

So I ask, what's the proper way of doing this?所以我问,这样做的正确方法是什么? This is a general case, however it concerns the general issue of having code which takes a long time to complete in a graphical application, while needing code to run sequentially.这是一般情况,但它涉及在图形应用程序中需要很长时间才能完成代码的一般问题,同时需要代码按顺序运行。 Qt deals with this by using signals, and having a QThread emit a finished signal. Qt 通过使用信号来处理这个问题,并让 QThread 发出finished的信号。 Does GTK have an equivalent? GTK 是否有等价物?

I'm aware that this could be partially solved using Queue, with a put() and get() in relevant functions, however I don't understand how to get this to work if the main thread is calling anything other than functions.我知道这可以使用队列部分解决,在相关函数中使用 put() 和 get(),但是如果主线程调用函数以外的任何东西,我不明白如何让它工作。

Your suggestion on how to do this was very close to the actual solution, but it's indeed not going to work.您关于如何执行此操作的建议非常接近实际解决方案,但它确实不起作用。

In essence, what you'll indeed want to do, is to run the long-running function in a different thread.本质上,您确实想要做的是在不同的线程中运行长时间运行的 function。 That'll mean you get 2 threads: one which is running the main event loop that (amongs other things) updates your UI, and another thread which does the long-running logic.这意味着您将获得 2 个线程:一个正在运行(除其他外)更新您的 UI 的主事件循环,另一个执行长时间运行的逻辑的线程。

Of course, that bears the question: how do I notify the main thread that some work is done and I want it to react to that?当然,这有一个问题:我如何通知主线程一些工作已经完成并且我希望它对此做出反应? For example, you might want to update the UI while (or after) some complex calculation is going on.例如,您可能希望在进行某些复杂计算时(或之后)更新 UI。 For this, you can use GLib.idle_add() from within the other thread.为此,您可以在另一个线程中使用GLib.idle_add() That function takes a single callback as an argument, which it will run as soon as it can ("on idle"). function 将单个回调作为参数,它将尽快运行(“空闲”)。

So a possibility to use here, would be something like this:所以在这里使用的可能性是这样的:

class Main(Gtk.Window):

    def __init__(self):
        super().__init__()

        self.button = Gtk.Button(label='button')
        self.add(self.button)
        self.button.connect('clicked', self.main_function)

        thread = threading.Thread(target=self.threaded_function)
        thread.daemon = True
        thread.start()

    def threaded_function(self):
        # Really intensive stuff going on here
        sleep(20)

        # We're done, schedule "on_idle" to be called in the main thread
        GLib.idle_add(self.on_idle)

    # Note, this function will be run in the main loop thread, *not* in this one
    def on_idle(self):
        second_normal_function()
        return GLib.SOURCE_REMOVE # we only want to run once

    # ...

For more context, you might want to read thepygobject documentation on threading and concurrency有关更多上下文,您可能需要阅读有关线程和并发的 pygobject 文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM