I am working with tkinter and within the __init__() method of my Windows class, where I launch two threads that execute a method.
My problem is that I can't figure out where to call .join() so that those threads don't continue their execution once the main window is destroyed through Tk().destroy() method.
Tkinter.Tk().mainloop() is blocks the program from continuing while the screen updates. After calling Tk().destroy(), it will unblock and continue the program as normal, allowing you to call thread.join() there. Here as a demo of for code executing after:
from tkinter import Tk
root = Tk()
def f():
root.destroy()
root.after(3000,f)
root.mainloop()
print("Join Thread Here")
Related
I am building a Tkinter GUI and I have a process that takes a while to complete, so I threaded it to prevent the GUI from hanging. Lets call the threaded function foo. Once foo is completed, I need to call another function, bar. bar needs to be called from the main thread (it uses a matplotlib method that does not work inside of a thread).
I can't seem to wrap my head around how I might do this. I thought about joining the thread, but that just causes the GUI to hang. I also thought about using a signal variable that I would change in the last line of foo to tell the rest of my program that it is done and its time to execute bar, but then I couldn't figure out how I could continuously check that variable in the main thread without hanging the GUI. Any ideas?
Using Python 3.7
You can use threading.Event() object to notify the main thread and use after() to call a function periodically to check the Event() object to determine when to call bar().
Below is a simple example:
import tkinter as tk
import threading
import time
def foo(event):
print('foo started')
time.sleep(5)
print('foo done')
# notify main thread
event.set()
def bar():
print('hello')
def check_event(event, callback):
print('.', end='')
if event.is_set():
# thread task is completed
callback()
else:
# check again 100 ms (adjust this to suit your case) later
root.after(100, check_event, event, callback)
root = tk.Tk()
# create the `Event()` object
event = threading.Event()
# start the checking
check_event(event, bar)
# start the thread task
threading.Thread(target=foo, args=(event,)).start()
root.mainloop()
I'm trying a GUI application in which I am using threading to create concurrently executed tasks. Here is the code:
from tkinter import *
from threading import *
import time
kill = False
def mainer():
global kill
while not kill:
maintext.set(value='bbb')
def quitfunc():
global kill
kill = True
time.sleep(2)
root.destroy()
root=Tk()
maintext=StringVar(value='aaa')
Thread(target=mainer).start()
root.protocol("WM_DELETE_WINDOW", quitfunc)
root.mainloop()
ISSUES:
As it currently stands, on closing the root window, the process doesn't stop. It keeps running. Even if I add an infinite loop to print isalive() for the mainer thread, it keeps saying True. Why does it not quit?
In case I add a print(kill) statement in the mainer function, I get one of the two outcomes:
If written above the maintext.set() statement, on quitting the window, the print stops getting executed but the thread still does not quit. Very very rarely, it does, which I am assuming must depend on which line the function was on when the quit function was executed.
If written below that statement, on quitting, the thread almost inevitably quits.
I have no clue what is happening here. Any help is appreciated.
If you make the thread a daemon it will die when the main thread dies, so you don't need any of the quit logic at all:
from tkinter import *
from threading import *
import time
def mainer():
while True:
maintext.set(value='bbb')
time.sleep(.1) # just so my CPU does not rail
root=Tk()
maintext=StringVar(value='aaa')
t = Thread(target=mainer)
t.daemon = True
t.start()
root.mainloop()
So I have two python threads running from inside a class. I have checked using
threading.active_count()
and it says both threads are running. The first thread includes a tkinter window which works fine. The second thread I am using as an event manager for the first window, which also works okay by itself. However, when I run the second thread alongside the first thread, the first thread does not work, ie. the window does not appear. This is even if the first thread is executed first. When I remove the infinite loop from the second thread, the first thread works again, can anyone explain this to me? Here is the class:
class Quiz(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def show(self, question):
self.question = quiz[question][0]
self.correct = quiz[question][1]
self.incorrectA = quiz[question][2]
self.incorrectB = quiz[question][3]
self.ref = quiz[question][4]
questionDisplay.config(text=self.question)
correctButton = "answer" + str(self.ref[0])
eval(correctButton).config(text=self.correct, command=lambda : check(True))
incorrect1 = "answer" + str(self.ref[1])
eval(incorrect1).config(text=self.incorrectA, command= lambda : check(False))
incorrect2 = "answer" + str(self.ref[2])
eval(incorrect2).config(text=self.incorrectB, command= lambda : check(False))
return self.correct
def run(self):
print("thread started")
print(threading.active_count())
while True:
print(questionQueue.qsize())
if questionQueue.qsize() >= 1:
pass
else:
pass
print("looped")
Thanks
From the code as currently shown it is not obvious where the problem lies. But keep the following in mind;
Tk is event-driven like basically all GUI toolkits. So for the GUI to work you need to run Tk's mainloop. The only pieces of your code that it runs in the main loop are the various callbacks attached to things like buttons, menus and timers.
Like most GUI toolkits Tk isn't thread-safe because of the overhead that would require. To keep it working properly, you should only call Tk functions and methods from one thread.
Python's threads are operating system threads. This means they are subject to operating system scheduling. And the OS sometimes gives more time to threads that are busy. So if a thread that is spinning in a busy-loop is pre-empted (as is done regularly), chances are that it ends up being run again instead of the GUI thread.
I'm building an application that does thousands (possibly millions) of calculations based off of what the user inputs. Because these calculations can take a long time, I want to use Python's multiprocessing module. Here is my dilemma; I want to set up a tkinter window with a cancel button to stop the calculations running throughout the processors I set up using Pool. I tried using threads to allow the popup window to run, but some funny things happen when I run the code.
When I press the 'start' button, the Pool starts going as expected. However, my popup window does not show even though it is on its own thread. Even weirder, when I tell my IDE (PyCharm) to stop the program, the popup window shows and the calculations are still running until I either press the 'exit' button from the popup window, or kill the program altogether.
So my questions are: Why won't my popup window show even though it is on its own thread? Am I utilizing the multiprocessing module correctly? Or is the problem something totally different?
import tkinter as tk
from tkinter import ttk
import random
import threading
import time
from multiprocessing import Pool
def f(x):
print(x*x)
time.sleep(random.randrange(1,5)) # simulates long calculations
# Extra math stuff here...
def processor():
global calc
calc = Pool(4)
calc.map(f, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
print("Done")
def calculation_start():
p = threading.Thread(target=progress_window) # Thread to open a new window for the user to cancel a calculation
p.start()
processor() # Calls the function to run the calculations
def progress_window():
# This is the window that pops up to let a user cancel a calculation.
popup = tk.Toplevel()
endButton = ttk.Button(popup, text="End", command=lambda :(calc.terminate()))
endButton.pack()
master = tk.Tk()
startButton = ttk.Button(master, text="Start", command=calculation_start)
startButton.pack()
tk.mainloop()
EDIT:
I tried switching the processor function to a thread instead of the progress_window function.
def calculation_start():
p = threading.Thread(target=processor) # Thread for the function to run the calculations
p.start()
progress_window() # Open a new window for the user to cancel a calculation
My popup window appears and the 'end' button looks like it stops the processor when pressed, but it never continues past that point. It's like it's stuck at calc.map(f, [1,2,3,...] in the processor() function.
From the Pool.map documentation:
It blocks until the result is ready.
That means that, yes, the work is being done on other threads, but the main thread (the one calling map) will not execute anything else until the other threads are complete. You want to use map_async and (for a long-running, interactive process), provide a callback for what to do when the pool is finished (probably hide the pop-up).
If you want your program to simply show the pop-up then exit, you want to use the AsyncResult returned by map_async. Set up an idle handler using after_idle (See tkinter universal widget methods and remember you'll need to manually re-add the handler after every call). After each idle call, you could call either AsyncResult.ready() or AsyncResult.get(<timeout>) to see if the pool has completed. Once it's finished, you're safe to process the results and exit.
I'm really lost...I open a window with two buttons, and when you click on the button called "REGISTER SOME KEY PRESSES" it runs the script called registerSomeKeyPresses.py, BUUUUT once finished I want to close that execution but keep the first window displaying...it's being impossible for me....
Please, i would reaaaally appreciate any help...
Thanks!
#!/usr/bin/env python
from Tkinter import *
import threading
v0 = Tk()
def finishApplication(): v0.destroy()
def registerSomeKeyPresses():
t = threading.Thread(target=execfile("registerSomeKeyPresses.py"))
t.start()
def waitAndRun(f): v0.after(200, f)
b1=Button(v0,text="TERMINAR APLICACION",command=lambda: finishApplication()).pack()
button_keyPresses=Button(v0,text="REGISTER SOME KEY PRESSES",command=lambda: waitAndRun(registerSomeKeyPresses())).pack()
v0.mainloop()
================ registerSomeKeyPresses.py ===========================
Do several things and last command:
io.quit()
When you destroy the instance of Tk, your programm will (and should) exit. If you want to create and destroy windows, create and destroy an instance of Toplevel while keeping the main window active. If you don't want to see the main window you can hide it.
Also, tkinter and threads don't mix very well. You cannot call any methods on any widgets from another thread. I've heard other people say you can call event_generate from another thread, but I think that's the only tkinter function you can call from another thread.
Edit 1
A second try as a response to your comment:
from Tkinter import *
from subprocess import call
import sys
t = Tk()
def click():
t.iconify()
try:
call([sys.executable, 'script.py'])
finally:
t.deiconify() # if it should close do t.quit() and t.destroy()
b = Button(t, command= click)
b.pack()
t.mainloop()
Old Version
What does that do?
================ registerSomeKeyPresses.py ===========================
v0.quit()
v0.destroy()
io.mainloop()
An other error is:
threading.Thread(target=execfile, args = ("registerSomeKeyPresses.py",))
if you really neeed a thread.
Do never mix tkinter mainloop things with threads. Threads can use event_generate - thats safe.