【tkGo】線程和裝飾器的使用案例

1 背景

在使用tkinter進行GUI編程時,有時會碰到界面未響應的情況,如下:

2 解決辦法

原因很有可能是執行的某個動作阻塞了線程,可通過使用threading.Thread解決(本例中是點擊了Go菜單下的開始選項導致的界面卡死)

2.1 解決方法1 - 使用線程封裝該動作

修改前代碼:

class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "開始"
    LABEL_END = "終止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

修改後代碼:

class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "開始"
    LABEL_END = "終止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    def start(self):
        def run():
            self.go = True
            while self.go:
                self.stdout("biu biu biu!", with_time=" - ")
                time.sleep(1)
        t = Thread(target=run)
        t.start()
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

2.2 解決方法2 - 使用裝飾器類

from functools import wraps
from threading import Thread


class ThreadRun(object):
    def __init__(self):
        pass

    def __call__(self, f):
        @wraps(f)
        def wrapped_f(*args, **kw):
            t = Thread(
                target=f,
                args=args,
                kwargs=kw
            )
            t.start()

        return wrapped_f
class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "開始"
    LABEL_END = "終止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    @ThreadRun()
    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

2.3 解決方法3 - 繼承類,添加裝飾器函數

from tkinter import Menu
from tkinter import DISABLED, NORMAL
from threading import Thread
from functools import wraps


class EMenu(Menu):
    def __init__(self, *args, **kw):
        self.stdout = kw["stdout"] if "stdout" in kw else print
        self.stderr = kw["stderr"] if "stderr" in kw else print
        for key in ["stdout", "stderr"]:
            if key in kw:
                del kw[key]
        super().__init__(*args, **kw)

    def thread_run(label_name):
        def run_decorator(f):
            @wraps(f)
            def wrapped_f(self, *args, **kw):
                def call():
                    self.entryconfig(label_name, state=DISABLED)  # 設置菜單選項禁用
                    f(self, *args, **kw)
                    self.entryconfig(label_name, state=NORMAL)  # 設置菜單選項啓用
                t = Thread(target=call)
                t.start()
            return wrapped_f
        return run_decorator
class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "開始"
    LABEL_END = "終止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    @EMenu.thread_run(LABEL_START)
    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

3 源碼在github上

https://github.com/TheUncleWhoGrowsBeans/tkGo

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章