Call a function in main thread once threaded function is completed - python

I am building a Tkinter GUI and I have a process that takes a while to complete, so I threaded it to prevent the GUI from hanging. Lets call the threaded function foo. Once foo is completed, I need to call another function, bar. bar needs to be called from the main thread (it uses a matplotlib method that does not work inside of a thread).
I can't seem to wrap my head around how I might do this. I thought about joining the thread, but that just causes the GUI to hang. I also thought about using a signal variable that I would change in the last line of foo to tell the rest of my program that it is done and its time to execute bar, but then I couldn't figure out how I could continuously check that variable in the main thread without hanging the GUI. Any ideas?
Using Python 3.7

You can use threading.Event() object to notify the main thread and use after() to call a function periodically to check the Event() object to determine when to call bar().
Below is a simple example:
import tkinter as tk
import threading
import time
def foo(event):
print('foo started')
time.sleep(5)
print('foo done')
# notify main thread
event.set()
def bar():
print('hello')
def check_event(event, callback):
print('.', end='')
if event.is_set():
# thread task is completed
callback()
else:
# check again 100 ms (adjust this to suit your case) later
root.after(100, check_event, event, callback)
root = tk.Tk()
# create the `Event()` object
event = threading.Event()
# start the checking
check_event(event, bar)
# start the thread task
threading.Thread(target=foo, args=(event,)).start()
root.mainloop()

Related

Display a countdown timer in TKinter while making the code BLOCKING but do not freeze the GUI

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()

Join Threads running inside Tkinter.Tk().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")

Tkinter after method / threading block

I have a strange issue with TKinter after() method. I'm calling function func_a() (blocking call that takes some ms) in main thread and func_b() in after() to read a value at regular interval. It works like a charm, I can get some updated value during func_a() execution
I do not need any graphical interface, so I do not use anymore TKinter, now I'm calling func_a() in main thread. I create a separate thread to call func_b(). The issue is that the call to func_a() stops the execution of func_b() separate thread. I need to wait for func_a() returns to have some periodic call of func_b(). I do not have source of func_a() and func_b() (python C bindings). But maybe some thread locking mechanism prevents func_b() call when func_a() is called.
The question is, what is implemententation behind tkinter after? How can I achieve same behavior as Tkinter after(): be able to call func_b() when func_a()is called, without using TKinter?
Code looks like that :
pos_th= threading.Thread(target=read_pos, args=(0.1,))
pos_th.daemon = True
pos_th_stop = False
pos_th.start()
func_a()
def read_pos(period):
while not pos_th_stop :
func_b()
time.sleep(period)
The question is, what is implemententation behind tkinter after?
When it comes down to it, it's really quite simple. Tkinter's mainloop method is little more than an infinite loop that waits for items to appear on the event queue. When it finds an event, it pulls it off of the queue and calls the handlers for that event.
after simply puts an item on the event queue. During each iteration of mainloop tkinter will examine the timestamp on the function added by after, and if the given amount of time has elapses, the function is pulled off of the queue and run.

How to make QtGui window process events whenever it is brought forward on the screen?

I'm using PyQt for Python, and am building a gui. I had a problem a few weeks ago where I had a function outside of the gui module modifying widgets within the gui (advanced progress bar, updating strings, etc.), and those modifications were not reflected in the gui until the function that had made the changes finished running.
The solution to this was to simply call app.processEvents() after doing whatever modifications I wanted, which would immediately update the graphics of the window.
But now I am wondering, is there a way to do this everytime the window is brought forward?
Let's say I have called a function that will be modifying the progress bar, but this function takes quite a while to run. Inbetween calling the function, and the progress bar modification, the app processes no events. So, it during this time, I pull up a Chrome window (or anything else), and then close it, my gui window is blank, just gray, until app.processEvents() is called again.
Is ther functionality in PyQt that allows me to detect whenever the window is brought to the front of all current windows?
You should look into QThread.
Threads allow you to run long, complicated tasks in a worker thread while a background thread keeps the GUI responsive, such as updating a QProgressBar, ensuring it responds to motion events.
The basic idea is this:
# load modules
import time
from PySide import QtCore, QtGui
# APPLICATION STUFF
# -----------------
APP = QtGui.QApplication([])
# THREADS
# -------
class WorkerThread(QtCore.QThread):
'''Does the work'''
def __init__(self):
super(WorkerThread, self).__init__()
self.running = True
def run(self):
'''This starts the thread on the start() call'''
# this goes over 1000 numbers, at 10 a second, will take
# 100 seconds to complete, over a minute
for i in range(1000):
print(i)
time.sleep(0.1)
self.running = False
class BackgroundThread(QtCore.QThread):
'''Keeps the main loop responsive'''
def __init__(self, worker):
super(BackgroundThread, self).__init__()
self.worker = worker
def run(self):
'''This starts the thread on the start() call'''
while self.worker.running:
APP.processEvents()
print("Updating the main loop")
time.sleep(0.1)
# MAIN
# ----
def main():
# make threads
worker = WorkerThread()
background = BackgroundThread(worker)
# start the threads
worker.start()
background.start()
# wait until done
worker.wait()
if __name__ == '__main__':
main()
The output you get is something like this, showing how it takes turns at doing the long calculation and updating the main loop:
0
Updating the main loop
1
Updating the main loop
2
Updating the main loop
3
Updating the main loop
4
Updating the main loop
5
Updating the main loop
6
Updating the main loop
Updating the main loop7
8
Updating the main loop
9
This along with a QFocusEvent override should allow you to do whatever you wish. But it's better to separate updating the GUI and running your desired long thread.
As for overriding the QFocusEvent you can do something as follows:
def focusInEvent(self, event):
event.accept()
# insert your code here
And if you choose to implement threads to avoid GUI blocking, you should read about the basics of threading (as threads have a lot of nuances unless you know about their potential pitfalls).

Running background tasks and then the GUI

okay so now i am almost finished with my little project with some bits left, that's running my background task and then showing my GUI.
class myGUIApp:
def __init()__:
....
def createwidgets():
....
if __name__ == "__main__":
import myBackgroundTasks
x = myBackgroundTasks()
x.startbackground1() <----- this is background task that doesn't need user interaction
x.startbackground2() <----- this is background task that doesn't need user interaction
MainWindow = myGUIApp()
MainWindow.show() <---- this is Pyside GUI
The issue is this, the GUI doesn't "show" until my 2 background tasks are finished, which can take quite some time as they are doing I/O jobs and grabber files from the internet. How should i go about this? Using python's multithread (inside the background task, i am also using multithreading)? Qthread? or multiprocessing module? or others? thanks for answering.
You could put it on a thread. Since the Qt gui runs in its own thread this is efficient use. Use a queue to pass back the results of x. The only trick is where and when do you need x? If you need it inside your gui then the best thing to do is use the gui's after method, if it has one, or whatever it's equivalent is. The point is you don't hog up all the resources continuously checking the queue for the output. If you put a while loop inside your gui, it will probably cause the gui to freeze.
from threading import Thread
from Queue import Queue
class myGUIApp:
def __init()__:
....
def createwidgets():
....
if __name__ == "__main__":
import myBackgroundTasks
QUEUE = Queue()
def queue_fun(q):
x = myBackgroundTasks()
x.startbackground1() <----- this is background task that doesn't need user interaction
x.startbackground2() <----- this is background task that doesn't need user interaction
q.put(x)
THREAD = Thread(target=queue_fun, args=QUEUE)
THREAD.start()
MainWindow = myGUIApp()
MainWindow.show() <---- this is Pyside GUI
# if you can wait until after mainloop terminates to get x, put this here
while THREAD.is_alive()
try:
x = QUEUE.get_nowait()
except Queue.Empty:
continue
# if you need this inside Pyside, then you should put this inside Pyside,
# but don't use while loop, use Qt after function and call self.wait
def wait(self):
try:
x = QUEUE.get_nowait()
except Queue.Empty:
self.after(5, self.wait)

Categories