首页 > 解决方案 > 如果线程在导入的文件中,则不会登录到 tkinter 窗口

问题描述

我有一个 tkinter 应用程序与两个不同的线程一起运行,这些线程使用队列登录它。

其中一个线程与 tkinter 应用程序位于同一代码文件中。另一个是从另一个文件导入的,即使它们的代码相似。我验证的是,只有在同一文件中定义的线程才能设法写入 UI。你知道为什么会这样吗?

主文件的代码是:

import time
import queue
import threading
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import ttk
import logging
from logging.handlers import QueueHandler

from foo import ImportedLoggerThread


logger = logging.getLogger(__name__)


class LoggerThread(threading.Thread):

    def __init__(self):
        super().__init__()
        self._stop_event = threading.Event()

    def run(self):
        logger.debug('LoggerThread: running')
        i = 0
        while not self._stop_event.is_set():
            logger.info("LoggerThread: iteration %d" % i)
            i += 1
            time.sleep(1)

    def stop(self):
        self._stop_event.set()


class LoggingWindow:
    def __init__(self, frame):
        self.frame = frame
        self.scrolled_text = ScrolledText(frame, height=12)
        self.scrolled_text.pack()
        self.log_queue = queue.Queue()
        self.queue_handler = QueueHandler(self.log_queue)
        logger.addHandler(self.queue_handler)
        # start polling
        self.frame.after(100, self.poll_log_queue)

    def write(self, record):
        msg = self.queue_handler.format(record)
        self.scrolled_text.insert(tk.END, msg + '\n')
        # Scroll to the bottom
        self.scrolled_text.yview(tk.END)

    def poll_log_queue(self):
        # Poll every 100ms
        while True:
            try:
                record = self.log_queue.get(block=False)
            except queue.Empty:
                break
            else:
                self.write(record)
        self.frame.after(100, self.poll_log_queue)


class App:

    def __init__(self, root):
        self.root = root
        frame = ttk.Labelframe(text="Log")
        frame.pack()
        self.console = LoggingWindow(frame)
        self.th = LoggerThread()
        self.th.start()
        self.imported = ImportedLoggerThread()
        self.imported.start()
        self.root.protocol('WM_DELETE_WINDOW', self.quit)

    def quit(self):
        self.th.stop()
        self.imported.stop()
        self.root.destroy()


def main():
    logging.basicConfig(level=logging.DEBUG)
    root = tk.Tk()
    app = App(root)
    app.root.mainloop()


if __name__ == '__main__':
    main()

对于第二个文件foo.py

import threading
import logging
import time

logger = logging.getLogger(__name__)


class ImportedLoggerThread(threading.Thread):

    def __init__(self):
        super().__init__()
        self._stop_event = threading.Event()

    def run(self):
        logger.debug('Imported: running')
        i = 0
        while not self._stop_event.is_set():
            logger.info("Imported: iteration %d" % i)
            i += 1
            time.sleep(2)

    def stop(self):
        self._stop_event.set()

提前致谢!

标签: pythonmultithreadingtkinter

解决方案


您在文件 ( logger = logging.getLogger(__name__)) 中定义了 2 个记录器实例,这会导致您的问题。如果您使用相同的记录器实例,它应该可以工作。这意味着在您的情况下,您应该将记录器实例从主文件传递到导入的模块(foo.py)。请看下面主文件中的固定foo.py和固定App类。

foo.py:

import threading
import time


class ImportedLoggerThread(threading.Thread):

    def __init__(self, my_logger):
        super().__init__()
        self._stop_event = threading.Event()
        self.my_logger = my_logger  # Should be passed from caller side.

    def run(self):
        self.my_logger.debug('Imported: running')
        i = 0
        while not self._stop_event.is_set():
            self.my_logger.info("Imported: iteration %d" % i)
            i += 1
            time.sleep(2)

    def stop(self):
        self._stop_event.set()

正如您在上面看到的“导入”模块使用获取记录器(它应该来自“主”文件)

应用类:

class App:

    def __init__(self, root):
        self.root = root
        frame = ttk.Labelframe(text="Log")
        frame.pack()
        self.console = LoggingWindow(frame)
        self.th = LoggerThread()
        self.th.start()
        self.imported = ImportedLoggerThread(my_logger=logger)  # Should be passed the defined logger instance.
        self.imported.start()
        self.root.protocol('WM_DELETE_WINDOW', self.quit)

    def quit(self):
        self.th.stop()
        self.imported.stop()
        self.root.destroy()

正如您在App类中看到的,定义的logger实例被传递给导入的ImportedLoggerThread类。

输出:

>>> python3 test.py 
DEBUG:__main__:LoggerThread: running
DEBUG:__main__:Imported: running
INFO:__main__:LoggerThread: iteration 0
INFO:__main__:Imported: iteration 0
INFO:__main__:LoggerThread: iteration 1
INFO:__main__:Imported: iteration 1
INFO:__main__:LoggerThread: iteration 2

图形用户界面:

图形用户界面


推荐阅读