Home > OS >  Threading in Tkinter and Python3
Threading in Tkinter and Python3

Time:04-04

I was adjusting this code to my needs in PyCharm where it worked well, without any exceptions and errors. 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 .

The traceback is: line 90, in run - line 51, in do action - line 30, in try_move

I tried to find the solution, but I only found mtTkinter for Python2.

Since I am new to threading, I don't know how to solve this problem and why it is only showing in Jupyter Notebook. Is it possible that Jupyter Notebook is the source of the problem?

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

CodePudding user response:

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)

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.

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

#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.
It may need also master.protocol("WM_DELETE_WINDOW", on_delete) to execute function when you click closing button [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()
  • Related