如何处理GUI,例如pysimplegui,在无限循环中运行时进入"无响应"状态?



我有通过 PySimpleGUI 运行的代码按预期执行,但问题是 GUI 在运行代码时显示为没有响应。我希望它显示为正在运行,而不在窗口标题中显示"未响应"。

我相信这是因为有一个无限循环的 While True:但这是为了运行 PySimpleGUI

。这是我循环的一个片段:

while True:
event, values = window.Read()
#print(event, values)
some_functions()
window.close()

我希望在按下按钮时窗口正常运行而不会在几秒钟后没有响应,它的工作方式与预期运行代码一样,但我想以某种方式隐藏没有响应的外观。

[ 编辑 2021 年 11 月 ]

使用StackOverflow来获取活动项目的答案存在相当大的问题,因此不断发展,希望以改进包的方式。

堆栈溢出答案充其量只能及时提供解决方案

有一个可怕的问题是,即使在撰写本文时,某些答案也不是由专家在该软件包中编写的。

当专家回答包是否演变的问题时,答案可能会变得不适用或最糟糕的是让用户走上一条注定要失败的道路,或者浪费时间解决以更优雅的方式解决的问题。

在这种特殊情况下,PySimpleGUI 已经发展到包含用于 Thread 标准库调用的简单包装器。 为什么?因为一个完全不熟悉编程的人遇到这个长时间操作的问题,不会有能力掌握线程,但仍然会遇到这个非常真实的问题,也许在他们的第一个 GUI 上。

2021 年 8 月,向 Window 对象添加了一个新方法。Window.perform_long_operation完全消除了新用户的负担。

perform_long_operation在 PySimpleGUI 食谱、电子食谱、演示程序中,可以为新手消除这一重要障碍,使他们能够继续解决他们原来的问题,而不是陷入他们在编程的前两周不准备解决的主题中。

我没有能力在StackOverflow中搜索我以前的答案并更新它们。 我敦促 PySimpleGUI 用户遵循文档中概述的支持步骤。 这也是我很少来这里回答问题的原因之一。 在其他地方提供支持。

关于现在过时的答案....

问题不在于循环。 问题在于您在调用 Read 之后调用的函数。 如果这些函数没有花费这么长时间,则不会看到"未响应"消息。 打掉那个电话,你的问题就消失了,对吧? 但是,当然你不能取消这个电话,因为这是你需要在GUI中做的事情。

有一个为此目的编写的演示程序。 任何时候你花费了太多的"时间",以至于操作系统抱怨,这意味着你花了太多。

这是一个经典的 GUI 问题,存在于所有 GUI SDK 中。 在 PySimpleGUI 事件循环或其他框架的回调中,您将收到这些类型的操作系统警告,表明您的程序已停止工作。

因为 PySimpleGUI 是基于 tkinter 的,所以你不能简单地将 GUI 作为另一个线程。 tkinter 不喜欢成为主线之外的任何东西。 因此,您需要将部分工作作为单独的线程运行,以便 GUI 继续获得 CPU 时间,从而操作系统将停止抱怨。

上次我只在SO上发布了一个链接,我被责骂了,所以我将在此处粘贴演示程序的代码。 您可以将其用作代码的设计模式。 如注释中所述,您可以通过在 repl.it 上运行代码来在线运行它以查看它的工作原理。

import queue
import threading
import time
# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
# To try something other than tkinter version, just comment out the first import and uncomment the one you want
import PySimpleGUI as sg
# import PySimpleGUIQt as sg
# import PySimpleGUIWx as sg
# import PySimpleGUIWeb as sg
"""
DESIGN PATTERN - Multithreaded Long Tasks GUI
Presents one method for running long-running operations in a PySimpleGUI environment.
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
The "long work" is contained in the thread that is being started.
A queue.Queue is used by the threads to communicate with main GUI code
The PySimpleGUI code is structured just like a typical PySimpleGUI program.  A layout defined,
a Window is created, and an event loop is executed.
What's different is that within this otherwise normal PySimpleGUI Event Loop, there is a check for items
in the Queue.  If there are items found, process them by making GUI changes, and continue.
This design pattern works for all of the flavors of PySimpleGUI including the Web and also repl.it
You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
"""

def long_operation_thread(seconds, gui_queue):
"""
A worker thread that communicates with the GUI through a queue
This thread can block for as long as it wants and the GUI will not be affected
:param seconds: (int) How long to sleep, the ultimate blocking call
:param gui_queue: (queue.Queue) Queue to communicate back to GUI that task is completed
:return:
"""
print('Starting thread - will sleep for {} seconds'.format(seconds))
time.sleep(seconds)                  # sleep for a while
gui_queue.put('** Done **')  # put a message into queue for GUI

######   ##     ## ####
##    ##  ##     ##  ##
##        ##     ##  ##
##   #### ##     ##  ##
##    ##  ##     ##  ##
##    ##  ##     ##  ##
######    #######  ####
def the_gui():
"""
Starts and executes the GUI
Reads data from a Queue and displays the data to the window
Returns when the user exits / closes the window
"""
gui_queue = queue.Queue()  # queue used to communicate between the gui and the threads
layout = [[sg.Text('Long task to perform example')],
[sg.Output(size=(70, 12))],
[sg.Text('Number of seconds your task will take'),sg.Input(key='_SECONDS_', size=(5,1)), sg.Button('Do Long Task', bind_return_key=True)],
[sg.Button('Click Me'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Window').Layout(layout)
# --------------------- EVENT LOOP ---------------------
while True:
event, values = window.Read(timeout=100)       # wait for up to 100 ms for a GUI event
if event is None or event == 'Exit':
break
elif event.startswith('Do'):
try:
seconds = int(values['_SECONDS_'])
print('Starting thread to do long work....sending value of {} seconds'.format(seconds))
threading.Thread(target=long_operation_thread, args=(seconds , gui_queue,), daemon=True).start()
except Exception as e:
print('Error starting work thread. Did you input a valid # of seconds? You entered: %s' % values['_SECONDS_'])
elif event == 'Click Me':
print('Your GUI is alive and well')
# --------------- Check for incoming messages from threads  ---------------
try:
message = gui_queue.get_nowait()
except queue.Empty:             # get_nowait() will get exception when Queue is empty
message = None              # break from the loop if no more messages are queued up
# if message received from queue, display the message in the Window
if message:
print('Got a message back from the thread: ', message)
# if user exits the window, then close the window and exit the GUI func
window.Close()

##     ##    ###    #### ##    ##
###   ###   ## ##    ##  ###   ##
#### ####  ##   ##   ##  ####  ##
## ### ## ##     ##  ##  ## ## ##
##     ## #########  ##  ##  ####
##     ## ##     ##  ##  ##   ###
##     ## ##     ## #### ##    ##
if __name__ == '__main__':
the_gui()
print('Exiting Program')

相关内容

最新更新