python - 如果线程在导入的文件中,则不会登录到 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()
提前致谢!
解决方案
您在文件 ( 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
图形用户界面: