[英](Python tkinter): RuntimeError: main thread is not in main loop
[英]How to make "nested multithreading" work in tkinter? [RuntimeError: main thread is not in main loop]
正如我在評論中提到的,背后的想法是這樣的:
它上面有一個根和一個按鈕,一旦單擊按鈕,將首先彈出一個啟動畫面,同時頂層的元素准備就緒,但根屏幕不會凍結。 有什么辦法可以做到這一點? 提前致謝!
from tkinter import *
import customtkinter
import threading
def splash_screen():
global splash_screen
splash_screen = Tk()
label = customtkinter.CTkLabel(splash_screen, text="PLEASE WAIT...")
label.pack(pady=30, padx=30)
def initiate():
# get elements of the toplevel
pass
def toplevel():
# second main window after root
pass
def func1():
# to avoid root freezing
threading.Thread(target=func2).start()
def func2():
thread = threading.Thread(target=initiate)
thread.start()
splash_screen()
# wait until toplevel is ready
thread.join()
splash_screen.destroy()
toplevel()
root = customtkinter.CTk()
button = customtkinter.CTkButton(root, command=func1)
button.pack(pady=10, padx=10)
root.mainloop()
追溯:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "D:\Python311\Lib\tkinter\__init__.py", line 861, in callit
func(*args)
File "D:\Python311\Lib\site-packages\customtkinter\windows\widgets\scaling\scaling_tracker.py", line 178, in check_dpi_scaling
if window.winfo_exists() and not window.state() == "iconic":
^^^^^^^^^^^^^^^^^^^^^
File "D:\Python311\Lib\tkinter\__init__.py", line 1139, in winfo_exists
self.tk.call('winfo', 'exists', self._w))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
不要在后台線程中操作 UI 對象。(即使tkinter
在某種程度上允許在后台線程中操作 UI 對象,這與其他 UI 框架不同,但最好避免這種情況。)
在后台線程中調用after_idle()
與主線程進行交互,如下例所示。 使用此模式,您不需要嵌套線程。
import time
import threading
import tkinter
worker_thread = None
def worker_entry():
def update_status(v):
# This will run in the main UI thread.
status_text.delete(1.0, 'end')
status_text.insert('end', v)
for i in range(30):
root.after_idle(update_status, f'i:{i}')
time.sleep(0.1)
def join_worker():
# This will run in the main UI thread.
global worker_thread
worker_thread.join()
worker_thread = None
start_worker_button['state'] = 'normal'
root.after_idle(join_worker)
def on_start_worker():
global worker_thread
worker_thread = threading.Thread(target=worker_entry)
worker_thread.start()
start_worker_button['state'] = 'disabled'
root = tkinter.Tk()
start_worker_button = tkinter.Button(root, text='start worker',
command=on_start_worker)
start_worker_button.pack()
status_text = tkinter.Text(root)
status_text.pack()
root.mainloop()
附帶說明一下,上述模式是 UI 框架的事實標准。 比如WPF中的Dispatcher.BeginInvoke()
,Qt中的QTimer::singleShot()
,Gtk中的gdk_threads_add_idle_full()
,Android中的Activity.runOnUiThread()
等。
以下是涵蓋 OP 場景的另一個示例,顯示了啟動畫面 window。模式與上述相同。
import time
import threading
import tkinter
worker_thread = None
def worker_entry():
def update_status(v):
text = splash_win.status_text
text.delete(1.0, 'end')
text.insert('end', v)
for i in range(30):
root.after_idle(update_status, f'i:{i}')
time.sleep(0.1)
def join_worker():
global worker_thread, splash_win
worker_thread.join()
worker_thread = None
splash_win.destroy()
splash_win = None
start_worker_button['state'] = 'normal'
root.after_idle(join_worker)
def on_start_worker():
global worker_thread, splash_win
splash_win = win = tkinter.Toplevel(root)
splash_win.status_text = text = tkinter.Text(win)
text.pack()
worker_thread = threading.Thread(target=worker_entry)
worker_thread.start()
start_worker_button['state'] = 'disabled'
root = tkinter.Tk()
start_worker_button = tkinter.Button(root, text='start worker',
command=on_start_worker)
start_worker_button.pack()
root.mainloop()
另一方面,我試驗了一個類似於其中一個 OP 的場景,其中在第一個后台線程中創建了第二個 Tk 根(Tcl 解釋器),並發現在第二個(嵌套的)后台線程中調用after_idle()
會導致問題。 通過查看源碼,發現目前的實現並不支持這種場景。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.