I'm working on a Tkinter Desktop Application project. I started stumbling when I need to use Threading which runs the backend code.
I know that in order to share the variables across threads we should be using a global variable. Below is the minimal code.
obj = None
class Frame:
def __init__(self, frame):
self.middle_frame = frame
self.start_button = ttk.Button(self.middle_frame, text='Start', command=self.start)
self.start_button.grid(row=0, column=0)
self.stop_button = ttk.Button(self.middle_frame, text='Stop', command=self.stop)
self.stop_button.grid(row=0, column=1)
self.stop_button.config(state='disabled')
def start(self):
self.thread = threading.Thread(target=self.start_connection)
self.thread.start()
self.start_button.config(state='disabled')
self.stop_button.config(state='normal')
def start_connection(self):
global obj
obj = MainManager() # Starts the Backend Loop
def stop(self):
global obj
obj.close_connection() # Want to break the loop here
self.thread.join()
self.stop_button.config(state='disabled')
self.start_button.config(state='normal')
While running this code, I get obj.close_connection() AttributeError:'NoneType' object has no attribute 'close_connection'. But I was expecting obj to become as an object of MainManager().
Where am I going wrong? Help me with this.
The problem with your code is nothing to do with tkinter or really with multithreading. The problem is that MainManager has the loop started inside the __init__ method, something like this:
class MainManager:
def __init__(self):
self.alive = True
# this part runs until stopped
self.start_doing_stuff()
def start_doing_stuff(self):
while self.alive:
sleep(1)
print('doing stuff')
def stop(self):
self.alive = False
Here is a code snippet with a similar error to yours:
from threading import Thread
from time import sleep
obj = None
class MainManager:
def __init__(self):
self.alive = True
self.start_doing_stuff()
def start_doing_stuff(self):
while self.alive:
sleep(1)
print('doing stuff')
def stop(self):
self.alive = False
def test():
global obj
print('about to assign to my_global')
obj = MainManager()
print('assigned to my_global')
print(f'{obj=}')
t = Thread(target=test)
t.start()
sleep(3)
print(f'{obj=}')
obj.stop()
t.join()
Because the __init__ method doesn't terminate until the stop method is called, the call to MainManager() in the statement obj = MainManager() never terminates, so obj is not assigned to. This means that when obj.stop() is called, obj is still None.
This can be fixed my making the __init__ method terminate, and putting the long-running code into a separate method that is called later:
from threading import Thread
from time import sleep
obj = None
class MainManager:
def __init__(self):
self.alive = True
def start_doing_stuff(self):
while self.alive:
sleep(1)
print('doing stuff')
def stop(self):
self.alive = False
def test():
global obj
print('about to assign to my_global')
obj = MainManager()
print('assigned to my_global')
obj.start_doing_stuff()
print(f'{obj=}')
t = Thread(target=test)
t.start()
sleep(3)
print(f'{obj=}')
obj.stop()
t.join()
Have you imported MainManager, have you run the start_connection()?
Also, you delegate the assignment to a thread. Are you sure this affects the same obj as the main thread? Maybe the main thread stays 'none' and the delegate changes the value in a copy. Add a log or print statement and make sure the obj gets assigned the MainManager() object and print the memory location of that object. In the main class print the memory location of the obj variable there as well. Check if it is the same.
This is how you print the memory address of a variable :
print(hex(id(your_variable)))
Related
Introduction
I have been made some script with threading inside of it to kill exe's for certain amount of time. The threading seems working fine but when im going to close the script, it freezes. My script has a "button" to execute and exit using TKinter
Problematic
The script become freezes and the thread stil going on the background. The thread contain loops for 30-60 seconds (killing exe's). It seems the script cant close properly because of the thread still going on and on.
Here's the thread class :
# Global Variable #
runBroke='taskkill /F /im RuntimeBroker.exe'
#Thread Class #
class Killizer(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def killer(self):
while not self.stop_threads.is_set():
def kill():
os.system(runBroke)
timer()
def timer():
time.sleep(60)
kill()
kill()
def exe(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.killer)
self.thread1.start()
def shut(self):
self.stop_threads.set()
self.thread1.join()
self.thread1 = None
How to properly close or enforce to exit that thread using TKinter Button?
You could use after making a timer to kill the application:
import time, os
import tkinter as tk
runBroke='taskkill /F /im RuntimeBroker.exe'
class KillingApp():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(self.root, text="")
self.label.pack()
self.kill()
self.root.mainloop()
def _kill(self):
print("running: taskkill")
os.system(runBroke)
def kill(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text="Last kill time: %s" % now)
self._kill()
# idle 5 seconds
self.root.after(5000, self.kill)
app=KillingApp()
use ... at the end
# Global Variable #
runBroke='taskkill /F /im RuntimeBroker.exe'
#Thread Class #
class Killizer(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def killer(self):
while not self.stop_threads.is_set():
def kill():
os.system(runBroke)
timer()
def timer():
time.sleep(60)
kill()
kill()
def exe(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.killer)
self.thread1.start()
def shut(self):
self.stop_threads.set()
self.thread1.join()
self.thread1 = None
it will close it all cleanly
Good day everyone, Im trying to call a method inside a thread class which should set an event flag inside that thread, resulting in the thread to stop running while the event is set. The current code kind of works, and calls the function, but the event flag does not seem to trigger inside the thread.
The thread is responsible for an operation to run when a button is pressed on the GUI, but it shouldn't run if the event is set.
A minimal version of my code:
import threading
import time
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD.UP)
global run = 0
class Pump_Thread(threading.Thread):
def __init__(self, interval=0.5):
super(Pump_Thread, self).__init__()
self._stop_event = threading.Event()
self.interval = interval
Pump_threads = threading.Thread(target=self.run, daemon=True)
Pump_threads.start()
def stop(self):
self._stop_event.set()
print("Things have stopped yo...")
def resume(self):
self._stop_event.clear()
print("And Now Things will resume")
def run(self)
while not self._stop_event.is_set():
if (run == 1):
#doing some stuff when bit set#
print("Yeah Yeah, I'm running")
class Page1(Page):
def __init__(self, *args, **kwargs):
#Some Initializing methods to load buttons and graphics
self.start_btn=tk.Button(self,height=32,width =80,command=self.Start)
self.start_btn.place(x=50, y=50)
self.reset_btn=tk.Button(self,height=32,width =80,command=self.Reset)
self.reset_btn.place(x=50, y=80)
def Start(self):
global run
run = 1 #<------Set Bit for code to run
def Reset(self):
d = Pump_Thread()
d.resume() #<-----Run Method inside thread
class Monitor_Thread(threading.Thread):
def __init__(self, interval=0.5):
self.interval = interval
Monitor_Threads = threading.Thread(target=self.Monitor_Thread, daemon=True)
Monitor_Threads.start()
def run(self)
while True:
if Condition == True:
d = Pump_Thread()
d.stop() #<-----Run Method inside thread
class Interrupt_Class(page):
def Input(self):
d = Pump_Thread()
d.stop() #<-----Run Method inside thread
GPIO.add_event_detect(18, GPIO.FALLING, callback=Input, bouncetime=300)
class MainView(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, *kwargs)
super().__init__()
p1 = Page1(self) #Other Pages Added here with navigation buttons to raise the page to the front of the GUI#
p1.show()
if __name__ == "__main__":
root = tk.TK()
main = MainView(root)
main.pack(side="top", fill="both", expand=True)
root.wm_geometry("400x400")
root.attributes("-fullscreen", False)
Thread1 = Pump_Thread()
Thread2 = Monitor_Thread()
root.mainloop()
When the interrupt is triggered, "Things have stopped yo..." is printed, so the method is called, but the process still starts when the GUI Button pressed, meaning the event didn't set. What would be the reason for this?
You seem to have many mistakes piled on top of one another.
You need to look at the many examples on stackoverflow of how to create and manage threads.
You might try something like this:
class Pump_Thread(threading.Thread):
def __init__(self, interval=0.5):
super().__init__()
self._stop_event = threading.Event()
self.interval = interval
def stop(self):
self._stop_event.set()
print("Things have stopped yo...")
def resume(self):
self._stop_event.clear()
print("And Now Things will resume")
def run(self)
while not self._stop_event.is_set():
print("Yeah Yeah, I'm running")
# other parts elided
if __name__ == "__main__":
root = tk.TK()
main = MainView(root)
Thread1 = Pump_Thread()
Thread1.start()
but now all other places which need to start and stop Pump_Thread need access to Thread1. So you should pass this into objects that are created.
class Interrupt_Class(page):
def __init__(self, pump):
self.pump = pump
def Input(self):
self.pump.stop() #<-----Run Method inside thread
ic = Interrupt_Class(Thread1)
You are calling:
d = Pump_Thread()
each time before calling: d.stop() which only makes that thread stop.
How is Pump_Thread instantiated and where do you store the reference to it?
In fact, you don't show how any of your classes are instantiated.
I'm making a game where I would like a timer to be displayed on the screen after the user clicks on 'NEW GAME', to keep track of how long they've been playing. I have a class that runs the timer fine by itself, but when I incorporate it into the rest of my game and then on top of that, try to display the updated values of the timer, no values in the UI are updated and the printout of the timer doesn't even occur in the terminal. I've tried running the timer in the same thread as the game-setup process and I've also tried creating a new thread to run the timer but neither work. The game loads up and functions fine, with the exception of the timer not counting upwards and not displaying the updated timer values. Where am I going wrong here?
Here is my standalone Timer class, which again, works fine by itself.
from PyQt5 import QtCore
import sys
def startThread(functionName, *args, **kwargs):
print(args)
if len(args) == 0:
t = threading.Thread(target=functionName)
else:
try:
t = threading.Thread(target=functionName, args=args, kwargs=kwargs)
except:
try:
if args is None:
t = threading.Thread(target=functionName, kwargs=kwargs)
except:
t = threading.Thread(target=functionName)
t.daemon = True
t.start()
class Timer(object):
def __init__(self):
super(Timer, self).__init__()
def start_timer(self):
print("Starting timer...")
Timer.timer = QtCore.QTimer()
Timer.time = QtCore.QTime(0, 0, 0)
Timer.timer.timeout.connect(self.tick)
Timer.timer.start(1000)
def tick(self):
Timer.time = Timer.time.addSecs(1)
self.update_UI('%s' % Timer.time.toString("hh:mm:ss"))
def update_UI(self, text_string):
print(text_string)
# This is where the text would be sent to try and update the UI
Timer().start_timer()
This is more or less how my game-setup class is structured - currently I'm showing the version that uses threading:
class BuildUI(PyQt5.QtWidgets.QMainWindow, Ui_game):
def __init__(self):
super(BuildUI, self).__init__()
self.setupUi(self)
self.easy_mode = 38
self.user_available_cells = []
self.start_easy_game.triggered.connect(lambda: self.setup_game(self.easy_mode))
def setup_game(self, hidden_count):
def create_game_board():
startThread(Timer().start_timer)
self.game = BuildGame()
#The BuildGame class is not deliberately not shown
startThread(create_game_board)
class GAME(object):
def __init__(self):
GAME.app = PyQt5.QtWidgets.QApplication(sys.argv)
GAME.UI = BuildUI()
GAME.UI.show()
GAME.app.exec_()
def main():
GAME()
if __name__ == '__main__':
main()
The key to getting this to work is by using signals. Leaving the Timer class exactly as it is, the only modifications to be done are in the initialization of the GAME class, a signal needs to be added at the beginning of the BuildUI class and then using emit() to trigger that signal just before the self.game = BuildGame() call.
class BuildUI(PyQt5.QtWidgets.QMainWindow, sudoku_ui.Ui_sudoku_game):
# This signal just triggers a msgbox to display, telling the user the game is loading
process_start = PyQt5.QtCore.pyqtSignal()
# this is called to automatically close the msgbox window
process_finished = PyQt5.QtCore.pyqtSignal()
# This signal, when called will start the timer
start_game_timer = PyQt5.QtCore.pyqtSignal()
def __init__(self):
super(BuildUI, self).__init__()
self.setupUi(self)
self.easy_mode = 38
self.user_available_cells = []
self.start_easy_game.triggered.connect(lambda: self.setup_game(self.easy_mode))
def setup_game(self, hidden_count):
def create_game_board():
self.game = BuildGame()
# Now that the game is built, the timer can start
# This is the emit which will start the timer
GAME.UI.start_game_timer.emit()
startThread(create_game_board)
class GAME(object):
def __init__(self):
GAME.app = PyQt5.QtWidgets.QApplication(sys.argv)
GAME.UI = BuildUI()
GAME.dialog_box = MsgPrompt()
# This is the key right here - initializing the timer class
GAME.timer = Timer()
# This line below attaches a function to the emit() call - which will kick off the timer
GAME.UI.start_game_timer.connect(GAME.timer.start_timer)
# The below referenced class is deliberately omitted from this post
GAME.UI.process_start.connect(GAME.dialog_box.show_dialog_box)
GAME.UI.process_finished.connect(GAME.dialog_box.hide_dialog_box)
GAME.UI.show()
GAME.app.exec_()
def main():
GAME()
if __name__ == '__main__':
main()
I am wondering if I can run a while loop inside a method of a Python class that can be stopped from another method.
For example, something like this:
from time import sleep
class example():
global recording
def __init__(self):
pass
def start(self):
global recording
recording = True
while recording:
print(1)
def stop(self):
global recording
recording = False
print("SLEEEPINGGGGGGGGGGG")
a = example()
a.start()
sleep(0.5)
a.stop()
But, it does not work, the loop does not stop.
EDIT
Since I do not want to create a Thread outside the class, I just tried this, but it doesn't work either.
from time import sleep
import threading
class example():
def __init__(self):
self.is_running = False
def start(self):
self.is_running = True
self.loop_thread = threading.Thread(target=self.main_loop)
def main_loop(self):
while self.is_running:
sleep(0.5)
print(1)
def stop(self):
self.is_running = False
print("SLEEEPINGGGGGGGGGGG")
a = example()
a.start()
sleep(3)
a.stop()
a.start() is an infinite loop. Since nothing runs at the same time, it just runs and never reaches the next statements.
You need to create a thread, like this
import time,threading
class example():
def __init__(self):
self.__recording = False
def start(self):
self.__recording = True
while self.__recording:
time.sleep(1)
print(1)
def stop(self):
self.__recording = False
a = example()
t = threading.Thread(target=a.start)
t.start()
time.sleep(5)
a.stop()
t.join()
note that I have used a member variable instead of a global. When the start method sees that the variable is True, it quits the loop. job done.
That works because I'm using sleep(). If you're running a pure python CPU intensive job that won't work because of python GIL
As suggested in comments, you can go one step further and inherit from threading.Thread method instead:
import time,threading
class example(threading.Thread):
def __init__(self):
threading.Thread.__init__(self,target=self.run)
self.__recording = False
def run(self):
self.__recording = True
while self.__recording:
time.sleep(1)
print(1)
def join(self):
self.__recording = False
threading.Thread.join(self)
a = example()
a.start()
time.sleep(5)
a.join()
Note that stop method is now replaced by join which signals the recording thread to stop then calls the parent join method. So when you join you stop the loop automatically first.
You have to start your thread after initiating it:
from time import sleep
import threading
class example():
def __init__(self):
self.is_running = False
def start(self):
self.is_running = True
self.loop_thread = threading.Thread(target=self.main_loop)
self.loop_thread.start() # <--- you forgot to start your thread
def main_loop(self):
while self.is_running:
sleep(0.5)
print(1)
def stop(self):
self.is_running = False
print("SLEEEPINGGGGGGGGGGG")
a = example()
a.start()
sleep(3)
a.stop()
I would like to transfer data to threading class, but I can't get what is wrong. The code below is from this question and I changed it a little.
This is a code:
import gtk, gobject, threading, time
gobject.threads_init()
class T(threading.Thread):
pause = threading.Event()
stop = False
def start(self, data, *args):
super(T, self).start()
def run(self, data):
while not self.stop:
self.pause.wait()
gobject.idle_add(lambda *a : self.rungui(data))
time.sleep(0.1)
def rungui(self, data):
print "printed"
print data
thread = T()
class Start:
def toggle_thread(self, data=None, *args):
if not thread.is_alive():
thread.start(data)
thread.pause.set()
self.button.set_label('Pause Thread')
return
if thread.pause.is_set():
thread.pause.clear()
self.button.set_label('Resume Thread')
else:
thread.pause.set()
self.button.set_label('Pause Thread')
def __init__(self):
thread = T()
window = gtk.Window()
self.button = gtk.ToggleButton('Start Thread')
data = 3
self.button.connect('toggled', lambda *a : self.start(data), None)
window.add(self.button)
self.button.show()
window.show()
def start(self, data=None):
self.toggle_thread(data)
def main(self):
gtk.main()
if __name__ == "__main__":
start = Start()
start.main()
What do I have to correct to get threading fully working?
Don`t work with gtk out of gui thread. That about:
gobject.idle_add(self.rungui)
Example at your link work fine, but need system kill command for termination.
And super() can`t bring arguments to run() function.
My threads initialization looks like this:
class VirtService(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def thread_loop(self):
while self.queue.qsize():
data_command = self.queue_get()
...
queue = Queue()
if __name__ == '__main__':
gobject.threads_init()
vs = VirtService(queue)
And you may use Queue for data translation to both directions. You may use also queue for command. In non-graphical thread create c++ poll() analog through Queue.qet(), and in gui thread queue.put()