如果程序很慢,Pygame windows 没有响应

问题描述

我正在编写一个程序来与计算机下棋。我有一个 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])

使用期货

您可以通过使用 concurrent.futures 来抽象出线程的复杂性,但要点是:您有一项任务(未来),您需要等待才能完成。

使用生成器

您可以将 engine_play() 转换为 generator 函数以避免使用线程,但您需要注意引擎函数时不时地将控制权交还给主循环。

>

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_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

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