I am trying to get my head around threading in python, I have pieced together a simple tkinter UI that can run a background task to better understand how it all works.
I seem to have got to the point where I can create a seperate thread and interact with the GUI but the problem comes when I try to carry out the task the second time.
I believe what I am missing is a way to tell the daemon when to stop the thread.
Here is my code thus far:
import threading
import tkinter as tk
from collections import deque
from threading import Thread
from random import randint
from time import sleep
class Emitter:
def __init__(self):
self.stop_event = threading.Event()
self.thread = Thread(target=self.emit_text)
self.running = False
def emit_text(self):
sleep(3)
messageQueue.append(f'Random number: {randint(0, 100)}')
# self.thread.set()
def start_emit(self):
ui.consume_text()
self.thread.setDaemon(True)
self.thread.start()
class UI:
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(self.root, text='Original text')
self.label.pack()
start = tk.Button(self.root, text='Start Download', command=lambda x=Emitter(): x.start_emit())
start.pack()
def consume_text(self):
try:
self.label['text'] = messageQueue.popleft()
except IndexError:
self.root.after(ms=1000, func=self.consume_text)
messageQueue = deque()
ui = UI()
ui.root.mainloop()
I have tried to understand other answers but I can't grasp how you tell the daemon/thread what it's condition to stop is.
The prgram works as intended the first time you click the button, but then if you try to click it again it gives the error RuntimeError: cannot set daemon status of active thread even if I have waited for the Emitter to finish it's task
Thanks to #Solomon Slow for their comment, the problem I had was I was trying to restart the thread but in fact I should have been creating a new one each time.
To fix the problem, I changed my class Emitter class to create a new thread each time start_emit() is called.
The fixed class:
class Emitter:
def __init__(self):
self.stop_event = threading.Event()
def emit_text(self):
sleep(3)
messageQueue.append(f'Random number: {randint(0, 100)}')
def start_emit(self):
ui.consume_text()
thread = Thread(target=self.emit_text)
thread.start()
I also removed the self.thread.setDaemon(True) line.
Related
I have the following problem: I would like to create a GUI with tkinter, that reacts to signals, sent from a socket. For example, I would like to be able to terminate the application, when an end signal is received.
For that purpose I have a function, running in a separate thread, that listens for signals and acts accordingly. However, when I try to destroy the tkinter-GUI, the programm stops, and gives this error message:
Fatal Python error: PyEval_RestoreThread: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)
Python runtime state: initialized
I have recreated this minimum working example giving the same behavior:
import tkinter as tk
import time
import threading
class Gui(tk.Frame):
"""Minimal GUI with only a button"""
def __init__(self, master: tk.Tk):
tk.Frame.__init__(self, master)
self.pack()
tk.Button(self, text='Spam').pack()
class Client:
"""Client for handling signals"""
def __init__(self, master: tk.Tk):
self.master = master
self.gui = Gui(self.master)
self.signal = None # Initialize signal
self.thread = threading.Thread(target=self.listen_thread)
self.running = True
self.thread.start()
def listen_thread(self):
"""Listen for signals and handle actions"""
while self.running:
signal = self.signal # Dummy signal, set by external method, instead of received message from socket
if signal == 'end': # End signal received
self.master.destroy() # Destroy tkinter GUI, error occurs here
self.running = False # Terminate while loop
else:
time.sleep(0.2)
def send_signal_after(receiver: Client, delay: float = 2.0):
"""Send a signal to the client after short delay"""
time.sleep(delay)
receiver.signal = 'end'
if __name__ == '__main__':
root = tk.Tk()
client = Client(root)
threading.Thread(target=send_signal_after, args=(client,)).start()
root.mainloop()
if client.thread: # Check if thread is still running, if so, wait for termination
client.thread.join()
I am running this on MacOS 12.1, Python 3.10.
Is there any other way to terminate the application? I know, I could probably use sys.exit(), but I would like to do this in a cleaner way.
Thank you!
So, to understand how to do it, I made an example:
This is the first file (the main one) :
import tkinter as tk
from tkinter import *
import threading
import file2 as file2
def func(gui):
# just some code around here
# start up the program
root = Tk()
# pass the root in the __init__ function from file2
mainGui = file2.file2Class(root)
# Start the new thread
theThread = threading.Thread(target=func, args=([mainGui]))
theThread.daemon = True
theThread.start()
# loop command
tk.mainloop()
And this is file2 :
import tkinter as tk
from tkinter import *
class file2Class:
root = None
def __init__(self, initialRoot):
self.root = initialRoot
self.createUI();
def createUI(self):
# This is an exit button
tk.Button(
self.root,
text="Exit",
font = "Verdana 10 bold",
fg="red",
command=self.root.destroy, # <- this is the exit command
width=25,
height=2).grid(row=0,column=0)
So, the most important thing is to make sure that you pass root in the args of the thread.
I hope I helped you!
I have a project where a passive GUI runs in its own thread and is manipulated by the main thread. Especially, the window is closed by the main thread using event_generate:
from tkinter import Tk
import threading
import time
import queue
q = queue.Queue()
class Window:
def __init__(self):
self.root = Tk()
self.root.title("test")
self.root.bind("<<custom_close_event>>", self.close)
def close(self, event):
print("quit")
self.root.destroy()
def create_window():
window = Window()
q.put(window)
window.root.mainloop()
print("###########")
# Window creation executed in different thread
t1 = threading.Thread(target=create_window)
t1.start()
window = q.get()
time.sleep(2)
window.root.event_generate("<<custom_close_event>>")
print("end")
The program crashes with the following output:
quit
###########
Tcl_AsyncDelete: async handler deleted by the wrong thread
[1] 21572 IOT instruction (core dumped) python window_test.py
According to this discussion, it seems that the order of objects cleanup in multithreaded environment is in fault. The advice to nullify objects (in my case window) and to call gc.collect did not solve the problem.
How should I do?
Instead of using a separate thread to create a second reference to Tk(),
Just inherit tk.Toplevel when you create the "Window" class.
This will allow you to have really as many windows as you want.
You can use tk.after in order to monitor processes and do pseudo-multithreading things. Here's an example of how to do that
class Window(Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent = parent
...
self.parent.after(1000, self.do_something)
def do_something(self):
...
<code>
...
self.parent.after(1000, self.do_something)
root = Tk()
Window(root)
root.mainloop()
Using #AndrewPye's answer but inheriting from Tk instead of Toplevel:
from tkinter import *
class Window(Tk):
def __init__(self, **kwargs):
super().__init__(**kwargs)
super().after(1000, self.do_something)
def do_something(self):
print("I am in a loop that runs every 1000ms = 1s")
super().after(1000, self.do_something)
root = Window()
root.mainloop()
I want to create a GUI in tkinter that not only executes commands when a button is pressed, but responds to the state of a larger script running in a separate thread.
I have really dug around and tried to find some information on message passing, and I have found some great info on the pickle module, using multiprocessing and its built in tools and also threading, and queuing. I have even dug into David Beazley's lesson on concurrency located here. I just can't get the syntax right on any of those methods.
I have broken down my code into a small functional unit that should launch a little tkinter window like this:
tkinter window
The code below has a "launchGUI" function that launches my tkinter GUI, a "myLoop" function that starts the threads and will also loop to drive my larger program later, right now it just rotates the blink variable. I also have a blinkCheck method in my class that checks the status of the blink variable in the class.
I don't know if I am even putting my message receiver in the right place. In the following example code I am just trying to pass a global variable into the class. I know it is getting into the class, because the blinkCheck() method works even though uncommenting that method crashes the window. However, with the method turned off the label in the GUI never changes. I think the window crashing is the least of my worries, it must be because i have another while loop running.
What is the correct way to get that number in Label to change?
Here is my example code:
import tkinter as tk
from tkinter import Frame, Label
import time
import threading
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.label.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def launchGUI():
root = tk.Tk()
root.title("My Blinker")
app1 = MyClass(root)
app1.mainloop()
def myLoop():
global blink
t1=threading.Thread(target=launchGUI)
t1.daemon = True
t1.start()
print("blink in blinker function is {}".format(blink))
while True:
if blink == 0:
blink = 1
else:
if blink == 1:
blink = 0
time.sleep(2.5)
if __name__=="__main__":
myLoop()
In your description you have mentioned something about involving buttons. I do not see that in your provided snippet. But with buttons it is possible to configure the label, i.e:
from tkinter import Label, Button
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.button = Button(master, text="Button", command=lambda: foo(self.label))
self.label.pack()
self.button.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def foo(self, label):
label.config(text=blink)
Conventionally, this would be the most simple way to configure a label within an active thread.
If anyone feels like this answer may not be fully correct, please do edit it because I am new to Stack Overflow!
First, the GUI must run in main thread, and must not blocked by a infinite loop. Use after instead. To communicate, use some appropriate object from threading, e.g. Event:
import tkinter as tk
import time
import threading
class MyClass(tk.Frame):
def __init__(self, master, event):
super().__init__(master)
self.master = master
self.event = event
self.label = tk.Label(master, text='')
self.label.pack()
self.after(100, self.blink_check)
def blink_check(self):
self.label['text'] = self.event.is_set()
self.after(100, self.blink_check)
def blink(event):
while True:
event.set()
time.sleep(2.5)
event.clear()
time.sleep(2.5)
def main():
root = tk.Tk()
root.title("My Blinker")
event = threading.Event()
t = threading.Thread(target=blink, args=(event,))
t.daemon = True
t.start()
frame = MyClass(root, event)
root.mainloop()
if __name__=="__main__":
main()
I have a small GUI which can start a program(pre installed application) as a subprocess in a thread, but now i want the program to run as a service when i press a button in the GUI.
Can i daemonise the subprocess. If it is not possible how can i do that?
The code for GUI is as follows:
#!/usr/bin/python
import Tkinter as tk
import subprocess
import os
import signal
import threading
class StageGui:
process=0
def __init__(self,parent):
self.process = None
self.f = tk.Frame(main, width=300, height=300)
self.b1=tk.Button(main,text='Start Medina',command=startmedina).pack(side='left',anchor='nw')
self.b2=tk.Button(main,text='Quit Medina',command=quitmedina).pack(side='left',anchor='nw')
self.xf = tk.Frame(self.f,relief='groove', borderwidth=2)
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
StageGui.process=subprocess.Popen(['pre xx'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True,preexec_fn=os.setsid)
return
def startmedina():
thread1 = StageGui.myThread(1, "Thread-1", 1)
thread1.start()
print "Exiting Main Thread"
return
main = tk.Tk()
stagegui=StageGui(main)
main.title('prototype')
main.mainloop()
I'm adding this as an answer since it's easier to post code this way, but it doesn't really address your original question. You might want to update your question based on our comments and this answer.
"Spinning a thread" just means creating a thread and starting it, which you're doing with this code:
thread1 = StageGui.myThread(1, "Thread-1", 1)
thread1.start()
You just need to change your code to do that in your button handler. I'm not familiar with tkinter, but it should be something like:
def button_handler():
thread1 = StageGui.myThread(1, "Thread-1", 1)
thread1.start()
b = Button(main, text="Spin Thread", command=button_handler)
b.pack()
That needs to happen after you do:
main = tk.Tk()
I'm trying to create a simple Python GUI (with Tkinter) with start button, running a while loop in a thread, and a stop button to stop the while loop.
I'm having issue with the stop button, which doesn't stop anything and frozen GUI once the start button is clicked.
See code below:
import threading
import Tkinter
class MyJob(threading.Thread):
def __init__(self):
super(MyJob, self).__init__()
self._stop = threading.Event()
def stop(self):
self._stop.set()
def run(self):
while not self._stop.isSet():
print "-"
if __name__ == "__main__":
top = Tkinter.Tk()
myJob = MyJob()
def startCallBack():
myJob.run()
start_button = Tkinter.Button(top,text="start", command=startCallBack)
start_button.pack()
def stopCallBack():
myJob.stop()
stop_button = Tkinter.Button(top,text="stop", command=stopCallBack)
stop_button.pack()
top.mainloop()
Any idea how to solve this? I'm sure this is trivial and must have be done thousands of times but I cannot find a solution myself.
Thanks
David
The code is calling run method directly. It will call the method in the main thread. To run it in a separated thread you should use threading.Thread.start method.
def startCallBack():
myJob.start()