Tkinter 中的线程和 Python3

Threading in Tkinter and Python3

我在 PyCharm 中根据我的需要调整此 code,它运行良好,没有任何异常和错误。当我在 Jupyter Notebook 中尝试它时它起作用了,但是当我关闭 Tkinter window 时,我得到异常 Exception in thread Thread-: 和错误 RuntimeError: main thread is not in main loop .

追溯是:第 90 行,在 运行 - 第 51 行,在执行操作 - 第 30 行,在 try_move

我试图找到解决方案,但我只找到了 Python2 的 mtTkinter。

由于我是线程的新手,我不知道如何解决这个问题,也不知道为什么它只显示在 Jupyter Notebook 中。 Jupyter Notebook 是否可能是问题的根源?

密码是:

from tkinter import *
import threading
import time

def render_grid():
    global specials, walls, WIDTH, x, y, player
    for i in range(x):
        for j in range(y):
            board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
            temp = {}
            cell_scores[(i, j)] = temp
    for (i, j, c, w) in specials:
        board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
    for (i, j) in walls:
        board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
        

def set_cell_score(state, action, val):
    global cell_score_min, cell_score_max


def try_move(dx, dy):
    global player, x, y, score, walk_reward, me, restart
    if restart:
        restart_game()
    new_x = player[0] + dx
    new_y = player[1] + dy
    score += walk_reward
    if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
        board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
                     new_y * WIDTH + WIDTH * 8 / 10)
        player = (new_x, new_y)
    for (i, j, c, w) in specials:
        if new_x == i and new_y == j:
            score -= walk_reward
            score += w
            if score > 0:
                print("Success! score: ", score)
            else:
                print("Fail! score: ", score)
            restart = True
            return
    # print "score: ", score


def call_up(event):
    try_move(0, -1)


def call_down(event):
    try_move(0, 1)


def call_left(event):
    try_move(-1, 0)


def call_right(event):
    try_move(1, 0)


def restart_game():
    global player, score, me, restart
    player = (0, y - 1)
    score = 1
    restart = False
    board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
                 player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)


def has_restarted():
    return restart


def start_game():
    master.mainloop()


master = Tk()
master.resizable(False, False)

cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]

board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04

walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}

discount = 0.3
states = []
Q = {}

for i in range(x):
    for j in range(y):
        states.append((i, j))

for state in states:
    temp = {}
    for action in actions:
        temp[action] = 0.1
        set_cell_score(state, action, temp[action])
    Q[state] = temp

for (i, j, c, w) in specials:
    for action in actions:
        Q[(i, j)][action] = w
        set_cell_score((i, j), action, w)


def do_action(action):
    s = player
    r = -score
    if action == actions[0]:
        try_move(0, -1)
    elif action == actions[1]:
        try_move(0, 1)
    elif action == actions[2]:
        try_move(-1, 0)
    elif action == actions[3]:
        try_move(1, 0)
    else:
        return
    s2 = player
    r += score
    return s, action, r, s2


def max_Q(s):
    val = None
    act = None
    for a, q in Q[s].items():
        if val is None or (q > val):
            val = q
            act = a
    return act, val


def inc_Q(s, a, alpha, inc):
    Q[s][a] *= 1 - alpha
    Q[s][a] += alpha * inc
    set_cell_score(s, a, Q[s][a])


def run():
    global discount
    time.sleep(1)
    alpha = 1
    t = 1
    while True:
        # Pick the right action
        s = player
        max_act, max_val = max_Q(s)
        (s, a, r, s2) = do_action(max_act)

        # Update Q
        max_act, max_val = max_Q(s2)
        inc_Q(s, a, alpha, r + discount * max_val)

        t += 1.0
        if has_restarted():
            restart_game()
            time.sleep(0.01)
            t = 1.0
        time.sleep(0.1)
        
render_grid()

master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)

me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
                            player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
                            width=1, tag="me")

board.grid(row=0, column=0)


t = threading.Thread(target=run)
t.setDaemon(True)
t.start()

start_game()

可能所有 GUI 都不喜欢在线程中 运行 并且小部件中的所有更改都应该在主线程中(但计算仍然可以在单独的线程中)

tkinter 中,您可以使用 master.after(milliseconds, function_name) 而不是 threadwhile - 定期循环到 运行 代码 - 这将像循环一样工作但是在当前线程中。

def run():
    global t
    
    # Pick the right action
    s = player
    max_act, max_val = max_Q(s)
    (s, a, r, s2) = do_action(max_act)

    # Update Q
    max_act, max_val = max_Q(s2)
    inc_Q(s, a, alpha, r + discount * max_val)

    t += 1.0
    if has_restarted():
        restart_game()
        time.sleep(0.01)
        t = 1.0

    # run again after 100ms
    master.after(100, run)

然后作为正常功能启动它

#master.after(100, run)  # run after 100ms

run()  # run at once

start_game()

完整的工作代码:

编辑:
您也可以使用全局变量 - 即。 running = True - 在销毁 GUI 之前停止循环。
当你点击关闭按钮[X]

时,它可能还需要master.protocol("WM_DELETE_WINDOW", on_delete)来执行功能
from tkinter import *
#import threading
import time

def render_grid():
    global specials, walls, WIDTH, x, y, player
    for i in range(x):
        for j in range(y):
            board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
            temp = {}
            cell_scores[(i, j)] = temp
    for (i, j, c, w) in specials:
        board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
    for (i, j) in walls:
        board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
        

def set_cell_score(state, action, val):
    global cell_score_min, cell_score_max


def try_move(dx, dy):
    global player, x, y, score, walk_reward, me, restart
    if restart:
        restart_game()
    new_x = player[0] + dx
    new_y = player[1] + dy
    score += walk_reward
    if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
        board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
                     new_y * WIDTH + WIDTH * 8 / 10)
        player = (new_x, new_y)
    for (i, j, c, w) in specials:
        if new_x == i and new_y == j:
            score -= walk_reward
            score += w
            if score > 0:
                print("Success! score: ", score)
            else:
                print("Fail! score: ", score)
            restart = True
            return
    # print "score: ", score


def call_up(event):
    try_move(0, -1)


def call_down(event):
    try_move(0, 1)


def call_left(event):
    try_move(-1, 0)


def call_right(event):
    try_move(1, 0)


def restart_game():
    global player, score, me, restart
    player = (0, y - 1)
    score = 1
    restart = False
    board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
                 player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)


def has_restarted():
    return restart


def start_game():
    master.mainloop()


def do_action(action):
    s = player
    r = -score
    if action == actions[0]:
        try_move(0, -1)
    elif action == actions[1]:
        try_move(0, 1)
    elif action == actions[2]:
        try_move(-1, 0)
    elif action == actions[3]:
        try_move(1, 0)
    else:
        return
    s2 = player
    r += score
    return s, action, r, s2


def max_Q(s):
    val = None
    act = None
    for a, q in Q[s].items():
        if val is None or (q > val):
            val = q
            act = a
    return act, val


def inc_Q(s, a, alpha, inc):
    Q[s][a] *= 1 - alpha
    Q[s][a] += alpha * inc
    set_cell_score(s, a, Q[s][a])


def run():
    global t
    
    # Pick the right action
    s = player
    max_act, max_val = max_Q(s)
    (s, a, r, s2) = do_action(max_act)

    # Update Q
    max_act, max_val = max_Q(s2)
    inc_Q(s, a, alpha, r + discount * max_val)

    t += 1.0
    if has_restarted():
        restart_game()
        time.sleep(0.01)
        t = 1.0

    # run again after 100ms
    if running:
        master.after(100, run)
    
def on_delete():
    global running
    
    running = False
    master.destroy()
    
# --- main ---

master = Tk()
master.resizable(False, False)

cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]

board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04

walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}

discount = 0.3
states = []
Q = {}

for i in range(x):
    for j in range(y):
        states.append((i, j))

for state in states:
    temp = {}
    for action in actions:
        temp[action] = 0.1
        set_cell_score(state, action, temp[action])
    Q[state] = temp

for (i, j, c, w) in specials:
    for action in actions:
        Q[(i, j)][action] = w
        set_cell_score((i, j), action, w)
        
render_grid()

master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)

me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
                            player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
                            width=1, tag="me")

board.grid(row=0, column=0)

#t = threading.Thread(target=run)
#t.setDaemon(True)
#t.start()

running = True
alpha = 1
t = 1
run()  # run at once
#master.after(100, run)  # run after 100ms

master.protocol("WM_DELETE_WINDOW", on_delete)
start_game()