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.
Related
A part of what I am doing, needs me to have background music in a tkinter game I created long time ago. I am using playsound.playsound{ Docs Here } to play music . I could use any other tool if needed to achieve what I intend like pyglet.media or pygame.mixer.
As the actual program was about 1k lines, I have tried adding an MCVE below.
Desired behavior & issue
The BGM (Background Music) should start when the app is launched - of course alongside a GUI, I would have a button to stop the BGM OR more preferably pause/play - I do think I need to use anything other than playsound.playsound for pause/play behavior.
The issue:: I can't seem to figure out how to make that communication between both the threads so that I can stop the music from playing or perhaps terminate the thread playing BGM - I could create a new thread when needed.
What I Have Tried
First up, I created two classes for GUI and BGM, each inheriting from threading.Thread - overridden the constructor and run() methods to do what I intend. Like this -
import tkinter as tk
import threading
from playsound import playsound
class BGM(threading.Thread):
def __init__(self, stopSignal):
threading.Thread.__init__(self)
self.stopSignal = stopSignal
def run(self):
while not self.stopSignal:
playsound('LoL.mp3') # to make this function play asynchronously, pass in False as second arg.
class GUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.root = tk.Tk()
def run(self):
tk.Button(self.root, text='stop').pack()
if __name__ == '__main__':
guiThread = GUI()
guiThread.start()
musicThread = BGM(0)
musicThread.start()
guiThread.root.mainloop()
But as you can see, it will just continue to play the song infinitely as long as the app is alive. There is no communication ( or active sync ) between GUI ( the button to stop song ) and BGM thread.
Now I tried using threading.Event() objects. The official docs on threading.Event() quotes:
This is one of the simplest mechanisms for communication between
threads: one thread signals an event and other threads wait for it.
An event object manages an internal flag that can be set to true with
the set() method and reset to false with the clear() method. The
wait() method blocks until the flag is true.
I'm not sure if this is useful for me to achieve what I intend but using threading.Event() did not do anything for me (or maybe I wasn't able to make use of it properly)
Now
What would you suggest to do to implement a BGM thread which can be stopped ( or paused/played preferably ) with a button(s) in the GUI thread.
Thank You For Any help Or Suggestions :)
I'm writing a tkinter app.
I want to use Thread to avoid the tkinter window freezing but actually I did not find solution.
A quick part of my code (simplify):
from threading import Thread
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
search_button = tk.Button(self, text='Print', command=self.Running)
search_button.grid(row=0, column=0)
def funct1(self):
print('One')
def funct2(self):
print('Two')
def CreateThread(self, item):
self.item = item
t = Thread(target=self.item)
t.start()
def Running(self):
self.CreateThread(self.funct1)
# How to wait for the end of self.CreateThread(self.funct1) ?
self.CreateThread(self.funct2)
if __name__ == '__main__':
myGUI = App()
myGUI.mainloop()
How to wait for the self.CreateThread(self.funct1) ending before running self.CreateThread(self.funct2).
With a queue ?
With something else ?
I already have take a look to Thread.join() but it freez the tkinter window.
Hope you can help me :)
IMO you should think differently about what "Thread" means. A thread is not a thing that you run. A thread is a thing that runs your code. You have two tasks (i.e., things that need to be done), and you want those tasks to be performed sequentially (i.e., one after the other).
The best way to do things sequentially is to do them in the same thread. Instead of creating two separate threads, why not create a single thread that first calls funct1() and then calls funct2()?
def thread_main(self):
funct1()
funct2()
def Running(self):
Threead(target=self.thread_main).start()
P.S.: This could be a mistake:
def CreateThread(self, item):
self.item = item
t = Thread(target=self.item)
t.start()
The problem is, both of the threads are going to assign and use the same self.item attribute, and the value that is written by the first thread may be over-written by the second thread before the first thread gets to used it. Why not simply do this?
def CreateThread(self, item):
Thread(target=item).start()
Or, since the function body reduces to a single line that obviously creates and starts a thread, why even bother to define CreateThread(...) at all?
You can synchronize threads using locks. Hard to give a concrete answer without knowing what needs to be done with these threads. But, locks will probably solve your problem. Here's an article on synchronizing threads.
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 have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()
I'm relatively new to wxPython (but not Python itself), so forgive me if I've missed something here.
I'm writing a GUI application, which at a very basic level consists of "Start" and "Stop" buttons that start and stop a thread. This thread is an infinite loop, which only ends when the thread is stopped. The loop generates messages, which at the moment are just output using print.
The GUI class and the infinite loop (using threading.Thread as a subclass) are held in separate files. What is the best way to get the thread to push an update to something like a TextCtrl in the GUI? I've been playing around with PostEvent and Queue, but without much luck.
Here's some bare bones code, with portions removed to keep it concise:
main_frame.py
import wx
from loop import Loop
class MainFrame(wx.Frame):
def __init__(self, parent, title):
# Initialise and show GUI
# Add two buttons, btnStart and btnStop
# Bind the two buttons to the following two methods
self.threads = []
def onStart(self):
x = Loop()
x.start()
self.threads.append(x)
def onStop(self):
for t in self.threads:
t.stop()
loop.py
class Loop(threading.Thread):
def __init__(self):
self._stop = threading.Event()
def run(self):
while not self._stop.isSet():
print datetime.date.today()
def stop(self):
self._stop.set()
I did, at one point, have it working by having the classes in the same file by using wx.lib.newevent.NewEvent() along these lines. If anyone could point me in the right direction, that'd be much appreciated.
The easiest solution would be to use wx.CallAfter
wx.CallAfter(text_control.SetValue, "some_text")
You can call CallAfter from any thread and the function that you pass it to be called will be called from the main thread.