跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 利用线程和事件系统更新 tkinter 的进度条,在同步的时候卡住了,求大佬帮看下。。。
未分類
13 2 月 2021

利用线程和事件系统更新 tkinter 的进度条,在同步的时候卡住了,求大佬帮看下。。。

利用线程和事件系统更新 tkinter 的进度条,在同步的时候卡住了,求大佬帮看下。。。

資深大佬 : seventhbible 1

最近在用 python 写工具给小组的其他成员使用,因为需要可以在 GUI 上输入不同的变量值做不同的判断处理所以最后写了一个 gui 。现在问题是,最后我希望写一个进度条,可以在主线程的 for-loop 中,每处理完一个任务后发送一个事件,给在另外一个线程的事件管理器,事件管理器可以立刻更新响应 GUI 的进度条的进度。 下面是我写的代码:

from queue import Queue, Empty from threading import * from tkinter import * import time from tkinter import ttk  EVENT_TYPE_1 = "Count" MAX_NUMBER = 10 CUR_NUMBER = 0  class event_manager:     def __init__(self):         self._eventQueue = Queue()         self._thread = Thread(target=self.Run)         self._handlers = {}         self._active = False      def Start(self):         self._active = True         self._thread.start()      def Run(self):         while self._active is True:             try:                 event = self._eventQueue.get(block=True, timeout=1)                 self.Process(event)             except Empty:                 pass      def Process(self, event):         if event.type in self._handlers:             for handler in self._handlers[event.type]:                 handler()         else:             pass      def Stop(self):         self._active = False         self._thread.join()      def addEventListenter(self, type_, handler):         try:             handlerList = self._handlers[type_]          except KeyError:             handlerList = []             self._handlers[type_] = handlerList          if handler not in handlerList:             handlerList.append(handler)      def removeEventListenter(self, type_, handler):         try:             handlerList = self._handlers[type_]             if handler in handlerList:                 handlerList.remove(handler)             if not handlerList:                 del self._handlers[type_]         except KeyError:             pass      def sendEvent(self, event):         self._eventQueue.put(event)   class Event:     def __init__(self, event_event_name, cur_done_task, type_=None):           self.type = type_         self._event_name = event_event_name         self._curDoneTask = cur_done_task   class EventSource:     def __init__(self, event_name, event_mgr, max_number, type):         self._event_name = event_name         self._event_manager = event_mgr         self._type = type         self._max_number = max_number      def count(self):         global CUR_NUMBER         for i in range(self._max_number):             CUR_NUMBER = i + 1             print("************ main thread start:now start process {} - count : {}".format(self._event_name, CUR_NUMBER))             event = Event("test", CUR_NUMBER, type_=self._type)             self._event_manager.sendEvent(event)             time.sleep(1)  class GUIListener(Tk):     def __init__(self):         super(GUIListener, self).__init__()          self.title("Progress GUI")         self.geometry("1200x805+600+100")         self.config(bg="#535353")         self.resizable(True, True)          self.progressBar = ttk.Progressbar(master=self, orient=HORIZONTAL, maximum=MAX_NUMBER, length=300)         self.progressBar.pack()         self.button = ttk.Button(self, text="Run", command=lambda: self.button_function(MAX_NUMBER))         self.button.pack()      def update_progress_value(self):         print("************Sub thread start: detect progress bar value is now...{}".format(self.progressBar['value']))         self.progressBar['value'] = CUR_NUMBER         self.progressBar.update_idletasks()         print("************Sub thread start: update progress bar value to...{}".format(CUR_NUMBER))      def button_function(self,max_number):         es = EventSource("eventSource", eventMgr, max_number, EVENT_TYPE_1)         es.count()  if __name__ == '__main__':     gui = GUIListener()      eventMgr = event_manager()     eventMgr.addEventListenter(EVENT_TYPE_1, gui.update_progress_value)     eventMgr.Start()      gui.mainloop() 

现在的问题是,返回的结果很奇怪,是这样的:

************ main thread start:now start process eventSource - count : 1 ************ main thread start:now start process eventSource - count : 2 ************ main thread start:now start process eventSource - count : 3 ************ main thread start:now start process eventSource - count : 4 ************ main thread start:now start process eventSource - count : 5 ************ main thread start:now start process eventSource - count : 6 ************ main thread start:now start process eventSource - count : 7 ************ main thread start:now start process eventSource - count : 8 ************ main thread start:now start process eventSource - count : 9 ************ main thread start:now start process eventSource - count : 10 ************Sub thread start: detect progress bar value is now...0.0 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 ************Sub thread start: detect progress bar value is now...10 ************Sub thread start: update progress bar value to...10 

请大佬指点一下,因为我希望的结果是类似下面这样立刻响应插入的顺序:

************ main thread start:now start process eventSource - count : 1 ************Sub thread start: detect progress bar value is now...0.0 ************Sub thread start: update progress bar value to...1 ************ main thread start:now start process eventSource - count : 2 ************Sub thread start: detect progress bar value is now...1.0 ************Sub thread start: update progress bar value to...2 ************ main thread start:now start process eventSource - count : 3 ************Sub thread start: detect progress bar value is now...2.0 ************Sub thread start: update progress bar value to...3 ... etc 

真的这个问题困扰了我蛮久了,希望大佬可以不吝赐教,指点问题发生在哪里,如何修改可以达到预期目的,感激不尽!

大佬有話說 (13)

  • 資深大佬 : ec0

    我改了 2 个地方

    1. class event_manager 里面的 __init__
    从 self._thread = Thread(target=self.Run)
    改成 self._thread = Thread(target=self.Run, daemon=True)

    2. class GUIListener 里面的 button_function
    从 es.count()
    改成 Thread(target=es.count).start()

    count 被 UI 线程调用,而 count 里面有 sleep,不要在 UI 线程写 sleep,另外开一个线程

  • 主 資深大佬 : seventhbible

    @ec0 我的天,大佬非常非常感谢您的指导!终于解决了这个困扰我很久的问题,谢谢谢谢谢谢!另外可以请大佬解答一下 daemon 这个是啥意思么?

  • 資深大佬 : ec0

    默认情况下 daemon=False
    这样当你的窗口关闭时,UI 线程关闭了,但是 event_manager 里面的线程还在运行,程序没有关闭
    你可以试试:运行程序,关闭窗口,但是在任务管理器里面可以看到 python 还在运行,只是没有显示窗口
    daemon=True 时,主线程关闭,daemon 线程也会关闭

  • 主 資深大佬 : seventhbible

    @ec0 原来如此,再次感谢大佬指教!

  • 主 資深大佬 : seventhbible

    @ec0 大佬可以追问一个问题嘛!就是现在的做法是把具体的订阅者的业务逻辑放到另外一个子线程去跑了,主线程只管 GUI 。那么如何做到我在 GUI 上点击一个按钮之后,在子线程开始处理具体的业务逻辑(譬如 count )且没有处理结束之前,锁住整个 GUI 的其他按钮模块不允许进行其他业务线程的处理?

  • 資深大佬 : ec0

    button[“state”] = “disabled” # 按钮不可点击
    button[“state”] = “normal” # 按钮可以点击

    比如,你可以在之前的 Thread(target=es.count).start() 前面加上
    self.button[“state”] = “disabled”

    在 class EventSource 的 count 函数 for 语句的后面加上
    for i in range(self._max_number):
    …
    gui.button[“state”] = “normal”

  • 主 資深大佬 : seventhbible

    @ec0 如果我的 GUI 上有 N 个按钮的话,就是每个按钮都要这样设定么

  • 主 資深大佬 : seventhbible

    其实不只是按钮,我的 gui 上还有其他的类似 combobox,text input entry 框等等可以互动的组件,如果想要一起锁住,请问有比较通用的方法么?

  • 資深大佬 : ec0

    是啊,很麻烦,不过你可以给 button 取有序的名字,比如 button0,button1,然后
    for i in range(n):
    (缩进)getattr(gui, “button”+str(i))[“state”] = “disabled”

    批量 disable 按钮

  • 資深大佬 : ec0

    我找到了这段代码,可以批量 disable TButton,更通用一些,改造一下 if 语句可以 disable 其他类型的控件,不过不是所有控件都有 state 属性的

    for child in gui.winfo_children():
    (缩进)if child.winfo_class() == ‘TButton’:
    (缩进)(缩进)child[‘state’] = ‘disabled’

  • 資深大佬 : geekzhu

    @ec0 我只想给这么有耐心的人点赞!

  • 主 資深大佬 : seventhbible

    @ec0 非常感谢大佬这么有耐心帮助我!我马上就去试试!

  • 資深大佬 : dblpx

    @ec0 点赞

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具