kivy app在使用线程时冻结

时间:2018-04-24 06:41:08

标签: python kivy

我有这个代码使用线程,但在某些时候,GUI冻结(按下按钮后)。

import threading
import Queue

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock

main_kv = """

<CustomLabel>:
    size_hint: (None, None)
    size: self.texture_size


<Main>:

    orientation: "vertical"

    Button:
        size_hint: (1, None)
        height: dp(70)
        on_press: root.spawn_threads()



"""



class CustomLabel(Label):
    pass



class Main(BoxLayout):

    def spawn_threads(self, *args):
        Clock.schedule_once(self.do_something, 1)

    def job(self):
        task = self.q.get()
        self.add_widget(CustomLabel(text=str(task)))
        self.q.task_done()

    def do_something(self, *args):
        data = [i for i in xrange(20)]

        self.q = Queue.Queue()

        for i in data:
            self.q.put(i)

        for _ in xrange(20):
            t = threading.Thread(target=self.job)
            t.daemon = 1
            t.start()

        self.q.join()


class TestApp(App):
    def build(self):
        Builder.load_string(main_kv)
        return Main()


TestApp().run()

我读过某个地方,我不能阻止GUI,否则它会冻结...也许“self.q.join()”阻止了GUI。有没有其他方法来实现队列join()方法,以便我不阻止GUI?

1 个答案:

答案 0 :(得分:1)

你实际上有两个问题。

首先,正如你猜测的那样:

  

我读过某个地方,我不能阻止GUI,否则它会冻结...也许“self.q.join()”阻止了GUI。有没有其他方法来实现队列join()方法,以便我不阻止GUI?

你对问题的看法是完全正确的,但你对解决方案的看法是错误的。

q.join阻止GUI的原因是它等待所有后台工作完成。你不能在事件回调中这样做。在您的回调返回之前,整个用户界面都会被冻结,等待您。

有三种方法:

  • 您可以生成另一个线程,只是为了等待队列,甚至是do_something的整个主体,然后只返回而不等待该线程。毕竟,在q.join之后你没有做任何事情,所以它发生时并不重要。

  • 或者你根本不能等。有什么需要同步或以其他方式响应这些线程完成他们的工作?看起来不像。

  • 最简单的说,你可以在这里使用futuresmultiprocessing.dummysubmit任务创建一个持久性线程池,而不是为每个创建一个新线程任务。 (您正在尝试在此处创建一个池,但是您不需要为每个操作创建一个单独的池。而且您很少需要与任务一样多的线程 - 当您这样做时,通常不需要池或队列。 )

但如果你解决了这个问题,你还会遇到另一个问题。虽然您的do_something函数不会与UI交互,但您正在生成的任务会执行。并且您不允许从后台线程与UI进行交互。

要解决此问题,您需要将UI工作移动到@mainthread函数:

@mainthread
def makelabel(self, text):
    self.add_widget(CustomLabel(text=text))

def job(self):
    task = self.q.get()
    self.makelabel(str(task))
    self.q.task_done()

但实际上,如果您在这些后台任务中执行的事情是创建窗口小部件,那么首先没有理由使用线程。你增加了一大堆开销和复杂性,并没有获得任何好处。如果您正在制作一堆网络请求,或者运行一堆子进程,或者做一些需要一段时间的其他工作并且主要是等待,那么您希望使用线程(或者再次使用线程池)。请注意@mainthread的示例完全相同:

self.req = UrlRequest(url='http://...', on_success=callback)

(A UrlRequest是一个致力于发出请求,等待响应,然后调用回调函数的线程。)