python+Tkinter+多線程的實例

python+Tkinter+多線程

界面和多線程一向是編程裡比較難的地方,常見的做法一般是界面一個線程,後臺新開一個工作線程,這兩個線程進行通信,這樣可以讓界面不至於為響應。

在python中可以利用隊列完成整體的架構設計。

直接給大傢看代碼吧,一個簡單實例,非常好的例子。

import Tkinter,time,threading,random,Queue
class GuiPart():
    def __init__(self,master,queue,endCommand):
        self.queue=queue
        Tkinter.Button(master,text='Done',command=endCommand).pack()
    def processIncoming(self):
        while self.queue.qsize():
            try:
                msg=self.queue.get(0)
                print msg
            except Queue.Empty:
                pass
class ThreadedClient():
    def __init__(self,master):
        self.master=master
        self.queue=Queue.Queue()
        self.gui=GuiPart(master,self.queue,self.endApplication)
        self.running=True
        self.thread1=threading.Thread(target=self.workerThread1)
        self.thread1.start()
        self.periodicCall()
    def periodicCall(self):
        self.master.after(200,self.periodicCall)
        self.gui.processIncoming()
        if not self.running:
            self.master.destroy()
    def workerThread1(self):
        #self.ott=Tkinter.Tk()
        #self.ott.mainloop()
        while self.running:
            time.sleep(rand.random()*1.5)
            msg=rand.random() 
            self.queue.put(msg)
    def endApplication(self):
        self.running=False
rand=random.Random()
root=Tkinter.Tk()
client=ThreadedClient(root)
root.mainloop()

tkinter與多線程問題

長時間執行後臺任務,UI會處於無響應狀態。在子線程裡更新UI狀態,聽說是不允許的。在哪個線程裡調用瞭tk.mainloop(),就隻能在哪個線程裡更新UI。

下例演示瞭如何更新。

import Tkinter as tk
from ttk import *
import time
import Queue, threading
class MainWindow:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title('Demo')
    def show(self):
        self.progress = tk.IntVar()
        self.progress_max = 100
        self.progressbar = Progressbar(self.root, mode='determinate', orient=tk.HORIZONTAL, variable=self.progress, maximum=self.progress_max)
        self.progressbar.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.progress.set(0)
        btn = tk.Button(self.root, text='start', command=self.start)
        btn.pack(fill=tk.BOTH, expand=True, padx=15, pady=5)
        self.btn = btn
        self.root.mainloop()
    def start(self):
        self.progress.set(0)
        self.btn.config(state=tk.DISABLED)
        self.thread_queue = Queue.Queue() # used to communicate between main thread (UI) and worker thread
        new_thread = threading.Thread(target=self.run_loop, kwargs={'param1':100, 'param2':20})
        new_thread.start()
        # schedule a time-task to check UI
        # it's in main thread, because it's called by self.root
        self.root.after(100, self.listen_for_result)
    def run_loop(self, param1, param2):
        progress = 0
        for entry in range(self.progress_max):
            time.sleep(0.1)
            progress = progress + 1
            self.thread_queue.put(progress)
    def listen_for_result(self):
        '''
        Check if there is something in the queue.
        Must be invoked by self.root to be sure it's running in main thread
        '''
        try:
            progress = self.thread_queue.get(False)
            self.progress.set(progress)
        except Queue.Empty: # must exist to avoid trace-back
            pass
        finally:
            if self.progress.get() < self.progressbar['maximum']:
                self.root.after(100, self.listen_for_result)
            else:
                self.btn.config(state=tk.NORMAL)
if __name__ == '__main__':
    win = MainWindow()
    win.show()

一個進度條。設定最大進度為100。在子線程裡每隔0.1秒更新一格。

總結

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: