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
Related
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)))
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 have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:
Click Start
Progressbar oscillates for 5 seconds
Progressbar stops
The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).
Here is my code so far:
class GUI:
def __init__(self, master):
self.master = master
self.test_button = Button(self.master, command=self.tb_click)
self.test_button.configure(
text="Start", background="Grey",
padx=50
)
self.test_button.pack(side=TOP)
def progress(self):
self.prog_bar = ttk.Progressbar(
self.master, orient="horizontal",
length=200, mode="indeterminate"
)
self.prog_bar.pack(side=TOP)
def tb_click(self):
self.progress()
self.prog_bar.start()
# Simulate long running process
t = threading.Thread(target=time.sleep, args=(5,))
t.start()
t.join()
self.prog_bar.stop()
root = Tk()
root.title("Test Button")
main_ui = GUI(root)
root.mainloop()
Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.
I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.
My question:
I can't figure out how to code it so that this command:
self.test_button = Button(self.master, command=self.tb_click)
calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.
If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?
When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.
If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:
Create a Queue object in the main thread
Create a new thread with access to that queue
Check periodically the queue in the main thread
Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().
import queue
class GUI:
# ...
def tb_click(self):
self.progress()
self.prog_bar.start()
self.queue = queue.Queue()
ThreadedTask(self.queue).start()
self.master.after(100, self.process_queue)
def process_queue(self):
try:
msg = self.queue.get_nowait()
# Show result of the task if needed
self.prog_bar.stop()
except queue.Empty:
self.master.after(100, self.process_queue)
class ThreadedTask(threading.Thread):
def __init__(self, queue):
super().__init__()
self.queue = queue
def run(self):
time.sleep(5) # Simulate long running process
self.queue.put("Task finished")
I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.
Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!
Here's class TkRepeatingTask and BackgroundTask:
import threading
class TkRepeatingTask():
def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
self.__tk_ = tkRoot
self.__func_ = taskFuncPointer
self.__freq_ = freqencyMillis
self.__isRunning_ = False
def isRunning( self ) : return self.__isRunning_
def start( self ) :
self.__isRunning_ = True
self.__onTimer()
def stop( self ) : self.__isRunning_ = False
def __onTimer( self ):
if self.__isRunning_ :
self.__func_()
self.__tk_.after( self.__freq_, self.__onTimer )
class BackgroundTask():
def __init__( self, taskFuncPointer ):
self.__taskFuncPointer_ = taskFuncPointer
self.__workerThread_ = None
self.__isRunning_ = False
def taskFuncPointer( self ) : return self.__taskFuncPointer_
def isRunning( self ) :
return self.__isRunning_ and self.__workerThread_.isAlive()
def start( self ):
if not self.__isRunning_ :
self.__isRunning_ = True
self.__workerThread_ = self.WorkerThread( self )
self.__workerThread_.start()
def stop( self ) : self.__isRunning_ = False
class WorkerThread( threading.Thread ):
def __init__( self, bgTask ):
threading.Thread.__init__( self )
self.__bgTask_ = bgTask
def run( self ):
try :
self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
except Exception as e: print repr(e)
self.__bgTask_.stop()
Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:
def tkThreadingTest():
from tkinter import Tk, Label, Button, StringVar
from time import sleep
class UnitTestGUI:
def __init__( self, master ):
self.master = master
master.title( "Threading Test" )
self.testButton = Button(
self.master, text="Blocking", command=self.myLongProcess )
self.testButton.pack()
self.threadedButton = Button(
self.master, text="Threaded", command=self.onThreadedClicked )
self.threadedButton.pack()
self.cancelButton = Button(
self.master, text="Stop", command=self.onStopClicked )
self.cancelButton.pack()
self.statusLabelVar = StringVar()
self.statusLabel = Label( master, textvariable=self.statusLabelVar )
self.statusLabel.pack()
self.clickMeButton = Button(
self.master, text="Click Me", command=self.onClickMeClicked )
self.clickMeButton.pack()
self.clickCountLabelVar = StringVar()
self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
self.clickCountLabel.pack()
self.threadedButton = Button(
self.master, text="Timer", command=self.onTimerClicked )
self.threadedButton.pack()
self.timerCountLabelVar = StringVar()
self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
self.timerCountLabel.pack()
self.timerCounter_=0
self.clickCounter_=0
self.bgTask = BackgroundTask( self.myLongProcess )
self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )
def close( self ) :
print "close"
try: self.bgTask.stop()
except: pass
try: self.timer.stop()
except: pass
self.master.quit()
def onThreadedClicked( self ):
print "onThreadedClicked"
try: self.bgTask.start()
except: pass
def onTimerClicked( self ) :
print "onTimerClicked"
self.timer.start()
def onStopClicked( self ) :
print "onStopClicked"
try: self.bgTask.stop()
except: pass
try: self.timer.stop()
except: pass
def onClickMeClicked( self ):
print "onClickMeClicked"
self.clickCounter_+=1
self.clickCountLabelVar.set( str(self.clickCounter_) )
def onTimer( self ) :
print "onTimer"
self.timerCounter_+=1
self.timerCountLabelVar.set( str(self.timerCounter_) )
def myLongProcess( self, isRunningFunc=None ) :
print "starting myLongProcess"
for i in range( 1, 10 ):
try:
if not isRunningFunc() :
self.onMyLongProcessUpdate( "Stopped!" )
return
except : pass
self.onMyLongProcessUpdate( i )
sleep( 1.5 ) # simulate doing work
self.onMyLongProcessUpdate( "Done!" )
def onMyLongProcessUpdate( self, status ) :
print "Process Update: %s" % (status,)
self.statusLabelVar.set( str(status) )
root = Tk()
gui = UnitTestGUI( root )
root.protocol( "WM_DELETE_WINDOW", gui.close )
root.mainloop()
if __name__ == "__main__":
tkThreadingTest()
Two import points I'll stress about BackgroundTask:
1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.
2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!
The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email
I have used RxPY which has some nice threading functions to solve this in a fairly clean manner. No queues, and I have provided a function that runs on the main thread after completion of the background thread. Here is a working example:
import rx
from rx.scheduler import ThreadPoolScheduler
import time
import tkinter as tk
class UI:
def __init__(self):
self.root = tk.Tk()
self.pool_scheduler = ThreadPoolScheduler(1) # thread pool with 1 worker thread
self.button = tk.Button(text="Do Task", command=self.do_task).pack()
def do_task(self):
rx.empty().subscribe(
on_completed=self.long_running_task,
scheduler=self.pool_scheduler
)
def long_running_task(self):
# your long running task here... eg:
time.sleep(3)
# if you want a callback on the main thread:
self.root.after(5, self.on_task_complete)
def on_task_complete(self):
pass # runs on main thread
if __name__ == "__main__":
ui = UI()
ui.root.mainloop()
Another way to use this construct which might be cleaner (depending on preference):
tk.Button(text="Do Task", command=self.button_clicked).pack()
...
def button_clicked(self):
def do_task(_):
time.sleep(3) # runs on background thread
def on_task_done():
pass # runs on main thread
rx.just(1).subscribe(
on_next=do_task,
on_completed=lambda: self.root.after(5, on_task_done),
scheduler=self.pool_scheduler
)
Here is a list of my answers on StackOverflow that relate to tkinter and threading:
Share objects between threads
Multiple threads update a variable and joining the main thread
An early and simple minded example using trace_variable
Set variable to console input
I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:
Click Start
Progressbar oscillates for 5 seconds
Progressbar stops
The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).
Here is my code so far:
class GUI:
def __init__(self, master):
self.master = master
self.test_button = Button(self.master, command=self.tb_click)
self.test_button.configure(
text="Start", background="Grey",
padx=50
)
self.test_button.pack(side=TOP)
def progress(self):
self.prog_bar = ttk.Progressbar(
self.master, orient="horizontal",
length=200, mode="indeterminate"
)
self.prog_bar.pack(side=TOP)
def tb_click(self):
self.progress()
self.prog_bar.start()
# Simulate long running process
t = threading.Thread(target=time.sleep, args=(5,))
t.start()
t.join()
self.prog_bar.stop()
root = Tk()
root.title("Test Button")
main_ui = GUI(root)
root.mainloop()
Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.
I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.
My question:
I can't figure out how to code it so that this command:
self.test_button = Button(self.master, command=self.tb_click)
calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.
If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?
When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.
If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:
Create a Queue object in the main thread
Create a new thread with access to that queue
Check periodically the queue in the main thread
Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().
import queue
class GUI:
# ...
def tb_click(self):
self.progress()
self.prog_bar.start()
self.queue = queue.Queue()
ThreadedTask(self.queue).start()
self.master.after(100, self.process_queue)
def process_queue(self):
try:
msg = self.queue.get_nowait()
# Show result of the task if needed
self.prog_bar.stop()
except queue.Empty:
self.master.after(100, self.process_queue)
class ThreadedTask(threading.Thread):
def __init__(self, queue):
super().__init__()
self.queue = queue
def run(self):
time.sleep(5) # Simulate long running process
self.queue.put("Task finished")
I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.
Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!
Here's class TkRepeatingTask and BackgroundTask:
import threading
class TkRepeatingTask():
def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
self.__tk_ = tkRoot
self.__func_ = taskFuncPointer
self.__freq_ = freqencyMillis
self.__isRunning_ = False
def isRunning( self ) : return self.__isRunning_
def start( self ) :
self.__isRunning_ = True
self.__onTimer()
def stop( self ) : self.__isRunning_ = False
def __onTimer( self ):
if self.__isRunning_ :
self.__func_()
self.__tk_.after( self.__freq_, self.__onTimer )
class BackgroundTask():
def __init__( self, taskFuncPointer ):
self.__taskFuncPointer_ = taskFuncPointer
self.__workerThread_ = None
self.__isRunning_ = False
def taskFuncPointer( self ) : return self.__taskFuncPointer_
def isRunning( self ) :
return self.__isRunning_ and self.__workerThread_.isAlive()
def start( self ):
if not self.__isRunning_ :
self.__isRunning_ = True
self.__workerThread_ = self.WorkerThread( self )
self.__workerThread_.start()
def stop( self ) : self.__isRunning_ = False
class WorkerThread( threading.Thread ):
def __init__( self, bgTask ):
threading.Thread.__init__( self )
self.__bgTask_ = bgTask
def run( self ):
try :
self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
except Exception as e: print repr(e)
self.__bgTask_.stop()
Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:
def tkThreadingTest():
from tkinter import Tk, Label, Button, StringVar
from time import sleep
class UnitTestGUI:
def __init__( self, master ):
self.master = master
master.title( "Threading Test" )
self.testButton = Button(
self.master, text="Blocking", command=self.myLongProcess )
self.testButton.pack()
self.threadedButton = Button(
self.master, text="Threaded", command=self.onThreadedClicked )
self.threadedButton.pack()
self.cancelButton = Button(
self.master, text="Stop", command=self.onStopClicked )
self.cancelButton.pack()
self.statusLabelVar = StringVar()
self.statusLabel = Label( master, textvariable=self.statusLabelVar )
self.statusLabel.pack()
self.clickMeButton = Button(
self.master, text="Click Me", command=self.onClickMeClicked )
self.clickMeButton.pack()
self.clickCountLabelVar = StringVar()
self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
self.clickCountLabel.pack()
self.threadedButton = Button(
self.master, text="Timer", command=self.onTimerClicked )
self.threadedButton.pack()
self.timerCountLabelVar = StringVar()
self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
self.timerCountLabel.pack()
self.timerCounter_=0
self.clickCounter_=0
self.bgTask = BackgroundTask( self.myLongProcess )
self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )
def close( self ) :
print "close"
try: self.bgTask.stop()
except: pass
try: self.timer.stop()
except: pass
self.master.quit()
def onThreadedClicked( self ):
print "onThreadedClicked"
try: self.bgTask.start()
except: pass
def onTimerClicked( self ) :
print "onTimerClicked"
self.timer.start()
def onStopClicked( self ) :
print "onStopClicked"
try: self.bgTask.stop()
except: pass
try: self.timer.stop()
except: pass
def onClickMeClicked( self ):
print "onClickMeClicked"
self.clickCounter_+=1
self.clickCountLabelVar.set( str(self.clickCounter_) )
def onTimer( self ) :
print "onTimer"
self.timerCounter_+=1
self.timerCountLabelVar.set( str(self.timerCounter_) )
def myLongProcess( self, isRunningFunc=None ) :
print "starting myLongProcess"
for i in range( 1, 10 ):
try:
if not isRunningFunc() :
self.onMyLongProcessUpdate( "Stopped!" )
return
except : pass
self.onMyLongProcessUpdate( i )
sleep( 1.5 ) # simulate doing work
self.onMyLongProcessUpdate( "Done!" )
def onMyLongProcessUpdate( self, status ) :
print "Process Update: %s" % (status,)
self.statusLabelVar.set( str(status) )
root = Tk()
gui = UnitTestGUI( root )
root.protocol( "WM_DELETE_WINDOW", gui.close )
root.mainloop()
if __name__ == "__main__":
tkThreadingTest()
Two import points I'll stress about BackgroundTask:
1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.
2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!
The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email
I have used RxPY which has some nice threading functions to solve this in a fairly clean manner. No queues, and I have provided a function that runs on the main thread after completion of the background thread. Here is a working example:
import rx
from rx.scheduler import ThreadPoolScheduler
import time
import tkinter as tk
class UI:
def __init__(self):
self.root = tk.Tk()
self.pool_scheduler = ThreadPoolScheduler(1) # thread pool with 1 worker thread
self.button = tk.Button(text="Do Task", command=self.do_task).pack()
def do_task(self):
rx.empty().subscribe(
on_completed=self.long_running_task,
scheduler=self.pool_scheduler
)
def long_running_task(self):
# your long running task here... eg:
time.sleep(3)
# if you want a callback on the main thread:
self.root.after(5, self.on_task_complete)
def on_task_complete(self):
pass # runs on main thread
if __name__ == "__main__":
ui = UI()
ui.root.mainloop()
Another way to use this construct which might be cleaner (depending on preference):
tk.Button(text="Do Task", command=self.button_clicked).pack()
...
def button_clicked(self):
def do_task(_):
time.sleep(3) # runs on background thread
def on_task_done():
pass # runs on main thread
rx.just(1).subscribe(
on_next=do_task,
on_completed=lambda: self.root.after(5, on_task_done),
scheduler=self.pool_scheduler
)
Here is a list of my answers on StackOverflow that relate to tkinter and threading:
Share objects between threads
Multiple threads update a variable and joining the main thread
An early and simple minded example using trace_variable
Set variable to console input
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()