简体   繁体   English

Tkinter 和 Python3 中的线程

[英]Threading in Tkinter and Python3

I was adjusting this code to my needs in PyCharm where it worked well, without any exceptions and errors.我在 PyCharm 中根据我的需要调整这段代码,它运行良好,没有任何异常和错误。 When I was trying it out in Jupyter Notebook it worked, but when I closed the Tkinter window, I get the exception Exception in thread Thread-: and the Error RuntimeError: main thread is not in main loop .当我在 Jupyter Notebook 中尝试它时它起作用了,但是当我关闭 Tkinter window 时,我得到异常Exception in thread Thread-:和错误RuntimeError: main thread is not in main loop

The traceback is: line 90, in run - line 51, in do action - line 30, in try_move回溯是:第 90 行,在运行中 - 第 51 行,在执行操作中 - 第 30 行,在 try_move 中

I tried to find the solution, but I only found mtTkinter for Python2.我试图找到解决方案,但我只找到了适用于 Python2 的 mtTkinter。

Since I am new to threading, I don't know how to solve this problem and why it is only showing in Jupyter Notebook.由于我是线程新手,我不知道如何解决这个问题,也不知道为什么它只显示在 Jupyter Notebook 中。 Is it possible that Jupyter Notebook is the source of the problem? Jupyter Notebook 是否可能是问题的根源?

The code is:代码是:

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()

Probably all GUIs don't like to run in threads and all changes in widgets should be in main thread (but calculations still can be in separated threads)可能所有的 GUI 都不喜欢在线程中运行,小部件中的所有更改都应该在主线程中(但计算仍然可以在单独的线程中)

In tkinter you could use master.after(milliseconds, function_name) instead of thread and while -loop to run code periodically - and this will works like loop but in current thread.tkinter ,您可以使用master.after(milliseconds, function_name)而不是threadwhile -loop 来定期运行代码 - 这将像循环一样工作,但在当前线程中。

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)

and later start it as normal function然后正常启动它 function

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

run()  # run at once

start_game()

Full working code:完整的工作代码:

EDIT:编辑:
You may also use global variable - ie.您也可以使用全局变量 - 即。 running = True - to stop looping before destroying GUI. running = True - 在销毁 GUI 之前停止循环。
It may need also master.protocol("WM_DELETE_WINDOW", on_delete) to execute function when you click closing button [X]它可能还需要master.protocol("WM_DELETE_WINDOW", on_delete)当你点击关闭按钮时执行 function [X]

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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM