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()
Related
Please read my question carefully - I know there are plenty of ways to implement a countdown timer on Tkinter without freezing the window, but all of the existing solutions also cause the code to be non-blocking. For my use case, I need to schedule a task to run automatically after time's up while keeping the GUI active (not frozen). My guess is that I need to somehow block the execution of the next task, but that will also freeze the GUI window. So is there any way out?
What I have so far:
root = Tk.Tk()
def countdown(time, msg='Counting down'):
def tick():
nonlocal time
status(f'{msg} ({60 - time}sec)')
time += 1
root.after(1000, tick)
where status() is simply a function that updates the text of some buttons.
The current count down function does not work by itself as I don't have a way to stop the after() after the timeout period.
The other parts of the program will be like:
countdown(10) # I need this line to be blocking or somehow prevents the code from going to next line
print('starting scheduled job...')
job()
I have tried to use threading but as I said earlier on, this causes the code to be non-blocking, and the moment I use Thread.join(), the entire GUI freezes again.
Currently, your question doesn't make a lot of sense to me. From what I understand you want your job() function to be called after the countdown.
Using thread for this is unnecessary. You can just use after and once the timer reaches 0 call the job() function.
Here is a minimal example
import tkinter as tk
def job():
status.config(text="starting job")
def countdown(time, msg='Counting down'):
time -= 1
status.config(text=f'{msg} ({time}sec)')
if time != 0:
root.after(1000, countdown, time)
else:
job() # if job is blocking then create a thread
root = tk.Tk()
status = tk.Label(root)
status.pack()
countdown(20)
root.mainloop()
Note: I may have overthought this and the other answer may actually be simpler perhaps
From my understanding you want to create a program that will after a certain time run another task, but neither the task, nor countdown should interfere with the GUI (but the task must run only after countdown), explanation in code comments:
# import what is needed
from tkinter import Tk, Button, Label
from threading import Thread
from queue import Queue, Empty
import time
# the function that the button will call to start the countdown
# and after that the task
def start_countdown():
# disable button so not to accidentally run the task again
# before it has even started
button.config(state='disabled')
# create a queue object
queue = Queue()
update_label(queue)
# set daemon=True to kill thread if the main thread exits
Thread(target=countdown, args=(queue, ), daemon=True).start()
# the task you want to do after countdown
def do_task():
for _ in range(10):
print('doing task...')
time.sleep(0.5)
# the actual countdown (btw using `time.sleep()` is more precise
# and only the thread will sleep)
# put data in queue so that it can easily be accessed
# from the main thread
def countdown(queue):
seconds = 10
for i in range(1, seconds + 1):
queue.put(f'Seconds left: {seconds + 1 - i}')
time.sleep(1)
queue.put('Starting task')
# place a sentinel to tell the reading part
# that it can stop
queue.put('done')
# do the task, this will run it in the same thread
# so it won't block the main thread
do_task()
# function to update the label that shows the users how many seconds left
def update_label(queue):
try:
# since block=False it will raise an exception
# if nothing is in queue
data = queue.get(block=False)
except Empty: # therefore except it and simply pass
pass
else:
# if no error was raised check if data is sentinel,
# if it is, stop this loop and enable the button (if needed)
if data == 'done':
button.config(state='normal')
return
# otherwise just update the label with data in the queue
label.config(text=data)
finally:
# and (almost) no matter what happens (nothing much should) loop this again
root.after(100, update_label, queue)
# basic tkinter setup
root = Tk()
root.geometry('300x200')
button = Button(root, text='Start countdown', command=start_countdown)
button.pack(expand=True)
label = Label(root)
label.pack(expand=True)
root.mainloop()
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")
I'm having a "deadlock-like" problem, where a thread needs to wait for the main thread to close before closing, but of course that one won't close until the other one closes.
My code is analogous to this:
import threading
from tkinter import *
def questions_funct():
while True:
if input("Do you want to close?:")=="YES":
root.destroy()
break
root = Tk()
questions_thread = threading.Thread(target=questions_funct)
questions_thread.start()
root.mainloop()
From what I've tested, nothing after root.destroy executes, but stuff after root.mainloop() does. So, it's clear root.destroy() is executed successfully which is further backed up by the window closing. Of course, the console is still open, a clear indicator that something is still running.
Might be important to add that all the testing was done with print("Test") stuff, so I'm definitely not sure if what I found out is true.
I've been banging my head against the wall in search of answers. Any help?
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.
Using Tkinter with Python on Linux, I'm trying to make Ctrl+C stop execution by using the KeyboardInterrupt Exception, but when I press it nothing happens for a while. Eventually it "takes" and exits. Example program:
import sys
from Tkinter import *
try:
root = Tk()
root.mainloop()
except:
print("you pressed control c")
sys.exit(0)
How can the program react quicker?
That is a little problematic because, in a general way, after you invoke the mainloop method you are relying on Tcl to handle events. Since your application is doing nothing, there is no reason for Tcl to react to anything, although it will eventually handle other events (as you noticed, this may take some time). One way to circumvent this is to make Tcl/Tk do something, scheduling artificial events as in:
from Tkinter import Tk
def check():
root.after(50, check) # 50 stands for 50 ms.
root = Tk()
root.after(50, check)
root.mainloop()
According to Guido van Rossum, this is because you are stuck in the Tcl/Tk main loop, while the signal handlers are only handled by the Python interpreter.
You can work around the problem, by binding Ctrl-c to a callback function:
import sys
import Tkinter as tk
def quit(event):
print "you pressed control c"
root.quit()
root = tk.Tk()
root.bind('<Control-c>', quit)
root.mainloop()