Python 从Tkinter运行线程并等待它';完成了

Python 从Tkinter运行线程并等待它';完成了,python,multithreading,unit-testing,testing,tkinter,Python,Multithreading,Unit Testing,Testing,Tkinter,我有一个tkinter应用程序(作为主线程运行),在其中我打开了一个新的顶级窗口——它是一个日志窗口,用于打印测试结果(测试是使用selenium webdriver执行的)。此对话框也是所有测试的调用方 所以我想显示对话框(作为顶层,整个应用程序还有一个窗口),运行测试,等待测试完成并打印结果,然后对另一个测试单元执行相同的操作。但我不想在测试期间让窗口冻结 我尝试过使用线程,但显然它可以像那样工作。在这种情况下,直到测试完成,对话框才会启动 这是对话框窗口的代码 class TestDial

我有一个tkinter应用程序(作为主线程运行),在其中我打开了一个新的顶级窗口——它是一个日志窗口,用于打印测试结果(测试是使用selenium webdriver执行的)。此对话框也是所有测试的调用方

所以我想显示对话框(作为顶层,整个应用程序还有一个窗口),运行测试,等待测试完成并打印结果,然后对另一个测试单元执行相同的操作。但我不想在测试期间让窗口冻结

我尝试过使用线程,但显然它可以像那样工作。在这种情况下,直到测试完成,对话框才会启动

这是对话框窗口的代码

class TestDialog(tkinter.Toplevel):

    def __init__(self, parent, tester, url):
        super().__init__(parent)        
        self.parent = parent
        self.webtester = tester;

        self.__initComponents()

        self.run(url)            

        self.wait_window(self)

    def __initComponents(self): 
        self.transient(self.parent)

        frame = tkinter.Frame(self)

        self._tarea = tkinter.Text(frame, state='disabled',wrap='none', width=55, height=25)

        vsb = tkinter.Scrollbar(frame, orient=tkinter.VERTICAL, command=self._tarea.yview)
        self._tarea.configure(yscrollcommand=vsb.set)


        self._tarea.grid(row=1, column=0, columnspan=4, sticky="NSEW", padx=3, pady=3)
        vsb.grid(row=1, column=4, sticky='NS',pady=3)
        frame.grid(row=0, column=0, sticky=tkinter.NSEW)

        frame.columnconfigure(0, weight=2)
        frame.rowconfigure(1, weight=1)

        window = self.winfo_toplevel()
        window.columnconfigure(0, weight=1) 
        window.rowconfigure(0, weight=1) 

        self.bind("<Escape>", self.close)

        self.protocol("WM_DELETE_WINDOW", self.close)
        self.grab_set()

    def appendLine(self, msg):
        self._tarea['state'] = 'normal'
        self._tarea.insert("end", msg+'\n')
        self._tarea['state'] = 'disabled'

    def run(self, url):

        self.appendLine("Runneing test #1...")

        try:
            thr = threading.Thread(target=self.webtester.urlopen, args=(url,))
            thr.start() 
        except:
            pass

        thr.join()

        self.webtester.urlopen(url)

        self.appendLine("Running test #2")        
        try: 
            thr = threading.Thread(target=self.webtester.test2)
            thr.start() 
        except:
            pass          

    def close(self, event=None):
        self.parent.setBackgroundScheme(DataTreeView.S_DEFAULT)
        self.parent.focus_set()
        self.destroy()

谢谢您的建议。

为了防止GUI冻结,您需要
self.run()
快速结束。 它需要生成一个线程,启动该线程,然后结束:

import Queue
sentinel = object()
root = tkinter.Tk()

...
def run(self, url):
    outqueue = Queue.Queue()
    thr = threading.Thread(target=self.run_tests, args=(url, outqueue))
    thr.start()
    root.after(250, self.update, outqueue)
现在,此线程运行的函数可以长时间运行:

def run_tests(self, url, outqueue):
    outqueue.put("Running test #1...")
    self.webtester.urlopen(url)
    outqueue.put("Running test #2")        
    self.webtester.test2()
    outqueue.put(sentinel)
但因为Tkinter期望所有GUI调用都来自单个线程,所以这个派生线程不能进行任何GUI调用。为了使其与GUI交互,您可以通过
队列.Queue
发送输出(例如状态更新消息),并同时让主Tkinter线程定期(通过调用
root.after
)监视此
队列.Queue


我自己刚刚发现了一些非常相似的东西,但是你的解决方案更优雅,帮助我理解了这个问题。谢谢你,先生!
def run_tests(self, url, outqueue):
    outqueue.put("Running test #1...")
    self.webtester.urlopen(url)
    outqueue.put("Running test #2")        
    self.webtester.test2()
    outqueue.put(sentinel)
def update(self, outqueue):
    try:
        msg = outqueue.get_nowait()
        if msg is not sentinel:
            self.appendLine(msg)
            root.after(250, self.update, outqueue)
        else:
            # By not calling root.after here, we allow update to
            # truly end
            pass
    except Queue.Empty:
        root.after(250, self.update, outqueue)