Python 如果程序运行缓慢,Pygame窗口将不响应

Python 如果程序运行缓慢,Pygame窗口将不响应,python,windows,pygame,Python,Windows,Pygame,我正在写一个程序来和电脑下棋。我有一个Pygame窗口,显示棋盘,让你选择你的移动。一旦你玩了你的招式,引擎就会开始计算招式树,以找到最好的招式。当它完成时,它会用它的移动进行回放。 问题是,当引擎“思考”时,Pygame窗口有一段时间没有更新(可能会更新很多,甚至一分钟或更长)。操作系统在不活动5秒后在窗口上提示消息“无响应”。 以下是一个最小可复制示例: import pygame from time import sleep def engine_play(): # Here t

我正在写一个程序来和电脑下棋。我有一个Pygame窗口,显示棋盘,让你选择你的移动。一旦你玩了你的招式,引擎就会开始计算招式树,以找到最好的招式。当它完成时,它会用它的移动进行回放。 问题是,当引擎“思考”时,Pygame窗口有一段时间没有更新(可能会更新很多,甚至一分钟或更长)。操作系统在不活动5秒后在窗口上提示消息“无响应”。 以下是一个最小可复制示例:

import pygame
from time import sleep

def engine_play():
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = 1+1


pygame.init()
clock = pygame.time.Clock()
clock.tick(60)
WIN = pygame.display.set_mode((500, 500))

engine_play()

# When the engine stops searching we get the events 
pygame.event.get()
当引擎停止并调用
pygame.event.get()
时,消息消失,一切正常。 主要问题是,如果在此期间单击窗口,Windows会警告您程序没有响应,并询问您是要关闭它还是等待


有人知道如何解决这个问题吗?提前谢谢

您需要确保操作系统窗口事件正在被处理,否则您会遇到可怕的“无响应”问题,正是因为您没有响应事件

一般来说,游戏总是有一个主循环,它一直在运行,并泵送事件、更新游戏逻辑、绘制屏幕等等。(根据应用程序的复杂性和设计方式,菜单可能会有另一个循环,但这不是重点。)

运用线程 要在程序中并发运行,您可能认为可以在另一个线程中泵送这些事件,但使用Pygame不安全,因此您需要在辅助线程中执行所有其他操作:

警告:
pygame.event.pump()
只应在初始化pygame.display的线程中调用

使用线程会有点麻烦,因为没有直接的方法从线程返回值,并且知道线程是否完成也需要一个事件。像这样的东西(可能有童车)应该可以解决问题

import threading

def engine_play(finish_event, output_list):
    # Here the engine searches for the best move for some time
    a = 0
    for _ in range(1000000000):
        a += _
    output_list.append(a)  # could also use a queue
    finish_event.set()

def run_engine_play():
    finish_event = threading.Event()
    output_list = []
    thread = threading.Thread(target=engine_play, args=(finish_event, output_list))
    thread.start()
    return (finish_event, output_list)

finish_event, output_list = run_engine_play()

while True:
    for event in pygame.event.get():
        pass  # handle events...
    if finish_event.is_set():
        print(output_list[0])
使用期货 您可以通过使用来抽象线程的复杂性,但要点是:您有一项任务(未来)需要等待完成

使用发电机 您可以将
engine\u play()
转换为一个函数,以避免使用线程,但您需要注意的是,引擎函数不时将控制权返回主循环


def engine_play():
    # Here the engine searches for the best move for some time
    a = 0
    for _ in range(1000000000):
        a += _
        if _ % 1000 == 0:  # every now and then, let the loop do other things
             yield None  # Not done yet...
    yield a

# Instantiate the generator
engine_play_gen = engine_play()


while True:
    for event in pygame.event.get():
        pass  # handle events...
    val = next(engine_play_gen)  # let the engine do its thing
    if val is not None:
        print(val)

有点混乱的解决方案,但它忽略了线程的使用。在
engine\u play
函数中,运行循环。在更新之前等待循环完成会导致程序出现大量延迟,足以让操作系统认为程序没有响应

这是它变得更有趣的地方。只要你在处理事件,操作系统就会认为你在响应。因此,您所要做的就是在整个程序中间歇性地进行一些事件调用。例如:

def engine_play():
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = a+1
        if a%500 == 0:
            pygame.event.get()
此函数每500个周期调用一次事件轮询器。这是足够的时间让操作系统认为你仍然在重新绑定,即使你没有

上述代码的问题在于您正在丢弃事件。如果要捕获这些事件,请创建自己的事件队列,并从中进行处理:

queue = []
def engine_play():
    global queue
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = a+1
        if a%500 == 0:
            queue += pygame.event.get()
...
while running:
    for event in (pygame.event.get()+queue):
        queue = []
        #handle events

在上面的程序中,它维护一个丢失的所有事件的列表,并在程序返回主循环时与当前事件一起处理。

多线程:让引擎运行一个单独的线程,主线程继续主循环。另请参阅,非常感谢!我实现了您的解决方案并解决了问题。我非常确定调用
pygame.event.poll()
就足够了。这不会丢弃队列,因此
get
以后仍然有完整的队列可用。
get
的队列大小限制为128,因此事件最终将被删除,特别是在搜索程序持续更长时间的情况下