首页 > 解决方案 > Python 3,Tkinter 和线程一个长时间运行的进程,OOP 风格

问题描述

我的小程序有一个潜在的长时间运行过程。从控制台执行此操作时这不是问题,但现在我想添加一个 GUI。理想情况下,我想使用 Tkinter (a) 因为它很简单,并且 (b) 因为它可能更容易跨平台实现。根据我的阅读和经验,(几乎)所有 GUI 都会遇到同样的问题。

通过我对线程和 GUI 主题的所有阅读,似乎有两个流。1 - 底层工作进程正在轮询(例如等待获取数据),以及 2 - 工作进程正在执行大量工作(例如,在 for 循环中复制文件)。我的程序属于后者。

我的代码有一个“层次结构”的类。
MIGUI 类处理 GUI 并与接口类 MediaImporter 交互。MediaImporter 类是用户界面(控制台或 GUI)和工作程序类之间的接口。Import 类是长期运行的工作者。它不知道存在接口或 GUI 类。

问题:单击开始按钮后,GUI 被阻止,所以我无法单击中止按钮。就好像我根本没有使用线程。我怀疑问题出在我在 startCallback 方法中启动线程的方式上。

我也尝试过线程化整个 MediaImporter 类的方法。请参阅注释掉的行。

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import threading
import time


class MIGUI():
    def __init__(self, master):
        self.master = master

        self.mediaImporter = MediaImporter()

        self.startButton = ttk.Button(self.master, text='Start', command=self.startCallback)
        self.startButton.pack()

        self.abortButton = ttk.Button(self.master, text='Abort', command=self.abortCallback)
        self.abortButton.state(['disabled'])
        self.abortButton.pack()

    def startCallback(self):
        print('startCallback')
        self.abortButton.state(['!disabled'])
        self.startButton.state(['disabled'])
        self.abortButton.update()  # forcing the update seems unnecessary
        self.startButton.update()
        #print(self.startButton.state())
        #print(self.abortButton.state())

        self.x = threading.Thread(target=self.mediaImporter.startImport)
        self.x.start()
        self.x.join()

        #self.mediaImporter.startImport()

        self.startButton.state(['!disabled'])
        self.abortButton.state(['disabled'])
        self.abortButton.update()
        self.startButton.update()
        #print(self.startButton.state())
        #print(self.abortButton.state())

    def abortCallback(self):
        print('abortCallback')
        self.mediaImporter.abortImport()
        self.startButton.state(['!disabled'])
        self.abortButton.state(['disabled'])


class MediaImporter():
#class MediaImporter(threading.Thread):
    """ Interface between user (GUI / console) and worker classes """
    def __init__(self):
        #threading.Thread.__init__(self)

        self.Import = Import()
        #other worker classes exist too

    def startImport(self):
        print('mediaImporter - startImport')
        self.Import.start()

    def abortImport(self):
        print('mediaImporter - abortImport')
        self.Import.abort()


class Import():
    """ Worker
        Does not know anything about other non-worker classes or UI.
    """
    def __init__(self):
        self._wantAbort = False

    def start(self):
        print('import - start')
        self._wantAbort = False
        self.doImport()

    def abort(self):
        print('import - abort')
        self._wantAbort = True    

    def doImport(self):
        print('doImport')
        for i in range(0,10):
            #actual code has nested for..loops
            print(i)
            time.sleep(.25)
            if self._wantAbort:
                print('doImport - abort')
                return


def main():
    gui = True
    console = False

    if gui:
        root = tk.Tk()
        app = MIGUI(root)
        root.mainloop()
    if console:
        #do simple console output without tkinter - threads not necessary
        pass

if __name__ == '__main__':
    main()

标签: pythonmultithreadingtkinter

解决方案


您的 GUI 被阻止的原因是因为您调用self.x.join(),它会阻塞直到doImport功能完成,请参阅加入文档。相反,我会调用join()你的abortCallback()函数,因为这会导致线程停止运行。


推荐阅读