Python3中,如何实现非阻塞的队列先后读取?
目测我的标题取错了,但是我实在不知道怎么用术语描述。
问题大概是这样的:在用 python 写一个五子棋 AI,那么就需要玩家下一步,电脑思考,电脑下一步。现在电脑思考大概需要 2 秒的时间。
代码大概是这样的:
que_game2ui = Queue(maxsize=2)
que_ui2game = Queue(maxsize=2)
class GUI:
def init(self, ui):
# self.bg = ui 的一个 canvas
pass
def on_click(self, event):
col = (event.x + GUICONF["half_gap"]) // GUICONF["gap"] - 1
row = (event.y + GUICONF["half_gap"]) // GUICONF["gap"] - 1
que_ui2game.put({
"option": "move",
"loc": (row, col),
})
def queue_handler(self):
try:
task = que_game2ui.get(block=False)
# 更新界面
self.bg.after(10, self.queue_handler)
except queue.Empty:
self.bg.after(10, self.queue_handler)
class GAME(Gomokuy):
def init(self, _gui)
# Gomokuy 是自己写的 AI 的类
# self.gui = _gui
pass
def moving(self, row, col):
if self.winner:
print("游戏结束")
return
ret = self.move((row, col))
if ret:
que_game2ui.put({
"game": self,
"info": "move"}, block=False)
pos2 = self.iterative_deepening(self.difficulty) # 这个函数很耗时
self.move(pos2)
que_game2ui.put({
"game": self,
"info": "move"}, block=False)
else:
que_game2ui.put({
"game": self,
"info": "Game Over"})
def queue_handler(self):
try:
task = que_ui2game.get(block=False)
if task["option"] == "move":
row, col = task["loc"]
self.moving(row, col)
self.gui.after(10, self.queue_handler)
except queue.Empty:
self.gui.after(10, self.queue_handler)
if name == ‘main’:
window = Tk()
gui = GUI(window)
t1 = threading.Thread(target=GAME, args=(window,))
t1.setDaemon = True
t1.start()
window.mainloop()
问题在于 GAME.moving 这里,分别有两次 put 操作,我设想的情况应该是:
- 第一次 put 之后,GAME 线程继续计算下一步 AI 应该怎么走( iterative_deepening )。
- GUI.queue_handler 拿到第一次 put 的内容之后,立即把玩家下的这一步绘制出来。
- n 秒后,GAME 算出来了,于是第二次 put,然后 GUI 再绘制,把电脑的落子也绘制出来。
但是根据实际症状,以及添加的调试信息来看,结果实际是这样的:
- 第一次 put 之后,GAME 算,算完之后才第二次 put。
- 第二次 put 之后,GUI 一次性把两次信息都拿出来(这个还看不出来,似乎 GUI 每次都只取到了最后一次 put,也就是走了两步之后的结果)绘制。
- 症状就是,点击之后没有任何反馈,然后一瞬间走两步。
感觉没有达到拆分线程的目的,请问代码思路是哪里有问题呢?
Python3中,如何实现非阻塞的队列先后读取?
3 回复
原因很简单,你的queue_handler都是通过 Tkinter 组件的after方法调用的,这个方法调用queue_handler方法是在主线程( UI 线程中),也就是说iterative_deepening阻塞了 UI 线程,所以卡住了


