PySimpleGUI线程内弹出交互窗口触发RuntimeError问题求助
问题:PySimpleGUI线程中弹窗引发RuntimeError
用PySimpleGUI开发GUI程序时,通过window.perform_long_operation或Threading模块能正常避免界面冻结,但在线程运行过程中弹出PopupYesNo等待用户输入以继续执行函数时,会触发RuntimeError: main thread is not in main loop错误。尝试将弹窗逻辑放在独立函数并通过queue传递结果,仍无法解决问题。目前将函数放在主GUI循环中可规避错误,但希望在点击“New WO”单选按钮打开的子GUI中实现该功能。
简化后代码
from threading import Thread import time import PySimpleGUI as sg import queue # 逐字符输出文本到元素,延迟0.08秒 def slow_type(el, text, delay=0.08): for character in text: el.send_keys(character) time.sleep(delay) def cont_auto(que): # 尝试过的代码: # cont_auto_value = sg.PopupYesNo("Continue?") # que.put(item=cont_auto_value) # new_wo_window.write_event_value(key="-MASUM-", value=que.put(item=cont_auto_value)) """ 试过把弹窗放到独立函数,用队列传值,还是不行 """ def google_search(que): # sg.cprint_set_output_destination(window=main_window, multiline_key="-MAIN-ML-") from selenium.webdriver.common.by import By from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from page_locators.PageLocators import dir_list opt = webdriver.ChromeOptions() opt.add_experimental_option("prefs", { 'download.default_directory': dir_list.new_folder, 'download.prompt_for_download': False, 'download.directory_upgrade': True, 'plugins.always_open_pdf_externally': True }) opt.add_argument("--kiosk-printing") driver = webdriver.Chrome(r"C:\Users\me_a1\Desktop\chromedriver.exe", chrome_options=opt) action = ActionChains(driver) driver.get("https://www.google.com") driver.maximize_window() # 等待2秒加载元素 time.sleep(2) item = que.get() # new_wo_window.write_event_value(key="-GOOGLE-", value=item) print(item) if item == "Yes": search_textbox = driver.find_element(By.XPATH, "//input[@class='gLFyf gsfi']") action.click(search_textbox).send_keys("real madrid").perform() time.sleep(2) driver.quit() else: search_textbox = driver.find_element(By.XPATH, "//input[@class='gLFyf gsfi']") action.click(search_textbox).send_keys("uefa").perform() time.sleep(2) driver.quit() que.task_done() def new_wo(): new_wo_layout = [[sg.Text("Search Term:".upper(), pad=((5, 0), (0, 0)))], [sg.InputText("", key="search", pad=((5, 0), (0, 0)))] ] layout_new_wo = [[sg.Column(new_wo_layout)], [sg.Button("Create WO", pad=((10, 0), (15, 0))), sg.Button("Cancel", pad=((20, 0), (15, 0)))]] new_wo_window = sg.Window(title=window_title(), layout=layout_new_wo, finalize=True, modal=True) while not new_wo_window.is_closed(): new_wo_event, new_wo_values = new_wo_window.read() search = [i.strip().upper() for i in str(new_wo_values["search"]).split("_.)(") if i != ""] if new_wo_event in [sg.WIN_CLOSED, "Cancel"]: new_wo_window.close() break elif new_wo_event == "Create WO": if all(len(new_wo_info_list) != 0 for new_wo_info_list in [search]): create_wo = sg.PopupYesNo("Start search?".upper(), "(please check your data before clicking yes)".upper(), f"Search terms: {''.join(search)}", modal=True, title="wo create data".upper()) if create_wo == "Yes": # 尝试过的线程代码: # que = queue.Queue() # function_thread = Thread(target=google_search, args=(que,), daemon=True) # function_thread.start() # continue_automation_popup_thread = Thread(target=cont_auto, args=(que,)) # continue_automation_popup_thread.start() # continue_automation_popup_thread.join() # 等待所有任务完成 # que.join() else: sg.Popup("New WO creation cancelled!".upper(), modal=True, title="wo cancel".upper()) else: sg.Popup("Please check that all data is provided".upper(), modal=True, title="Missing Data".upper()) def window_title(): title = [] for key, value in main_values.items(): if value: title.append(str(key).upper()) return title[0] # 主窗口布局 sg.theme_global("DarkBlack1") text_list = ["New WO"] layout = [[sg.Radio(str(text_list[i].upper()), enable_events=True, key=str(text_list[i]), group_id=0)] for i in range(0, len(text_list))] layout += [[sg.Multiline(size=(65, 23), key="-MAIN-ML-", pad=((5, 0), (0, 0)))], [sg.Button("Cancel", pad=((5, 0), (10, 6)))]] layout += [[sg.Menubar(menu_definition=[["SETTINGS", ("COLOR SCHEME", ["Dark Mode", "Light Mode", "Rose Pink", "Bright Red", "Midnight Blue"])]])]] layout_frame = [[sg.Frame(title="Select Option".upper(), title_color="yellow", layout=layout)]] main_window = sg.Window(title="Work Order Automation".upper(), layout=layout_frame, element_justification='c', finalize=True) while True: main_event, main_values = main_window.read() # 关闭窗口或点击Cancel时退出程序 if main_event == sg.WIN_CLOSED or main_event == "Cancel": break if main_event == "New WO": new_wo() # 这里是要运行线程并弹出弹窗的子GUI continue
报错信息
Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90> Traceback (most recent call last): File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__ if self._tk.getboolean(self._tk.call("info", "exists", self._name)): RuntimeError: main thread is not in main loop Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90> Traceback (most recent call last): File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__ if self._tk.getboolean(self._tk.call("info", "exists", self._name)): RuntimeError: main thread is not in main loop Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90> Traceback (most recent call last): File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__ if self._tk.getboolean(self._tk.call("info", "exists", self._name)): RuntimeError: main thread is not in main loop Exception in thread Thread-2 (cont_auto): Traceback (most recent call last): File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1009, in _bootstrap_inner self.run() File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\threading.py", line 946, in run self._target(*self._args, **self._kwargs) File "C:\Users\me_a1\Desktop\WO_Automation\GUI\testing.py", line 18, in cont_auto cont_auto_value = sg.PopupYesNo("Continue?") File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 20106, in popup_yes_no return popup(*args, title=title, button_type=POPUP_BUTTONS_YES_NO, background_color=background_color, File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 19390, in popup button, values = window.read() File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10072, in read results = self._read(timeout=timeout, timeout_key=timeout_key) File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10143, in _read self._Show() File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 9883, in _Show StartupTK(self) File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 16817, in StartupTK root = tk.Toplevel(class_=window.Title) File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2650, in __init__ BaseWidget.__init__(self, master, 'toplevel', cnf, {}, extra) File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2601, in __init__ self.tk.call( RuntimeError: main thread is not in main loop
解决方法
错误根源是所有PySimpleGUI(基于tkinter)的GUI操作必须在主线程执行,子线程不能直接创建弹窗或操作GUI元素。正确的做法是让子线程通知主线程弹出窗口,再把用户选择的结果传回子线程。
具体实现步骤
- 子线程执行到需要用户交互的节点时,用
window.write_event_value给主线程发送自定义事件,传递弹窗需求。 - 主线程在事件循环中捕获该自定义事件,弹出
PopupYesNo获取用户输入。 - 通过队列将用户选择的结果传回子线程,让子线程继续执行后续逻辑。
修改后的关键代码
1. 调整google_search函数,通知主线程弹窗
def google_search(que, new_wo_window): # ... 省略初始化浏览器的代码 ... driver.get("https://www.google.com") driver.maximize_window() time.sleep(2) # 通知主线程弹出确认窗口 new_wo_window.write_event_value("-ASK_CONTINUE-", "") # 等待主线程传回用户选择 item = que.get() # ... 省略后续搜索逻辑 ...
2. 修改new_wo函数的事件循环,处理自定义事件
def new_wo(): new_wo_layout = [[sg.Text("Search Term:".upper(), pad=((5, 0), (0, 0)))], [sg.InputText("", key="search", pad=((5, 0), (0, 0)))] ] layout_new_wo = [[sg.Column(new_wo_layout)], [sg.Button("Create WO", pad=((10, 0), (15, 0))), sg.Button("Cancel", pad=((20, 0), (15, 0)))]] new_wo_window = sg.Window(title=window_title(), layout=layout_new_wo, finalize=True, modal=True) que = queue.Queue() # 创建队列用于线程间传值 while not new_wo_window.is_closed(): new_wo_event, new_wo_values = new_wo_window.read() # 处理子线程发来的弹窗请求 if new_wo_event == "-ASK_CONTINUE-": cont_auto_value = sg.PopupYesNo("Continue?") que.put(cont_auto_value) elif new_wo_event == "Create WO": search = [i.strip().upper() for i in str(new_wo_values["search"]).split("_.)(") if i != ""] if all(len(new_wo_info_list) != 0 for new_wo_info_list in [search]): create_wo = sg.PopupYesNo("Start search?".upper(), "(please check your data before clicking yes)".upper(), f"Search terms: {''.join(search)}", modal=True, title="wo create data".upper()) if create_wo == "Yes": # 启动线程,传入队列和子窗口对象 function_thread = Thread(target=google_search, args=(que, new_wo_window), daemon=True) function_thread.start() # ... 省略其他事件处理代码 ...
核心原理
- 子线程仅负责耗时的非GUI操作(如Selenium自动化),遇到用户交互需求时,通过事件通知主线程。
- 主线程统一处理所有GUI操作,弹窗获取输入后用队列传递结果给子线程。
- 既避免了界面冻结,又符合tkinter的主线程GUI操作规则,彻底解决
RuntimeError问题。
内容的提问来源于stack exchange,提问作者Md. Masum Omar Jashim
相关产品推荐
相关产品推荐

