I had this code which uses threading but at some point, the GUI freezes (after I pressed the button).
import threading
import Queue
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock
main_kv = """
<CustomLabel>:
size_hint: (None, None)
size: self.texture_size
<Main>:
orientation: "vertical"
Button:
size_hint: (1, None)
height: dp(70)
on_press: root.spawn_threads()
"""
class CustomLabel(Label):
pass
class Main(BoxLayout):
def spawn_threads(self, *args):
Clock.schedule_once(self.do_something, 1)
def job(self):
task = self.q.get()
self.add_widget(CustomLabel(text=str(task)))
self.q.task_done()
def do_something(self, *args):
data = [i for i in xrange(20)]
self.q = Queue.Queue()
for i in data:
self.q.put(i)
for _ in xrange(20):
t = threading.Thread(target=self.job)
t.daemon = 1
t.start()
self.q.join()
class TestApp(App):
def build(self):
Builder.load_string(main_kv)
return Main()
TestApp().run()
I've read somewhere that I must not block the GUI or else it will freeze... Maybe the "self.q.join()" is blocking the GUI. Is there another way to implement queue join() method so that I'm not blocking the GUI?
You actually have two problems here.
First, as you surmised:
I've read somewhere that I must not block the GUI or else it will freeze... Maybe the "self.q.join()" is blocking the GUI. Is there another way to implement queue join() method so that I'm not blocking the GUI?
You're absolutely right about the problem, but you're on the wrong track about the solution.
The reason q.join is blocking the GUI is that it's waiting until all of your background work is finished. You can't do that in an event callback. Until your callback returns, the entire UI is frozen, waiting for you.
There are three ways around this:
You can spawn another thread just to wait on the queue, or even to do the entire body of do_something, and then just return without waiting for that thread. After all, you're not doing anything after the q.join, so it doesn't really matter when it happens.
Or you could just not wait at all. Does anything need to synchronize on or otherwise respond to these threads finishing their work? It doesn't seem like it.
Simplest of all, you could just create a persistent threadpool, using futures or multiprocessing.dummy and just submit tasks to it here, instead of creating a new thread for each task. (You're trying to create a pool here, but you don't need a separate pool for each operation. And you rarely want as many threads as tasks—when you do, you generally don't want a pool or a queue.)
But if you solve that, you've still got another problem. While your do_something function doesn't interact with the UI, the tasks you're spawning do. And you're not allowed to interact with the UI from background threads.
To fix this one, you want to move the UI work into a #mainthread function:
#mainthread
def makelabel(self, text):
self.add_widget(CustomLabel(text=text))
def job(self):
task = self.q.get()
self.makelabel(str(task))
self.q.task_done()
But really, if the only thing you're doing in these background tasks is creating a widget, there's no reason to use threads in the first place. You're adding a bunch of overhead and complexity, and not getting any benefit. You want to use threads (or, again, a thread pool) if you're making a bunch of network requests, or running a bunch of subprocesses, or doing some other work that takes a while and mostly involves waiting around. Notice that the example for #mainthread does exactly that:
self.req = UrlRequest(url='http://...', on_success=callback)
(A UrlRequest is a thread dedicated to making a request, waiting for the response, and then calling a callback function.)
Related
I would like to make it so that I can see the progress of the execution of the loop, but the ProgressBar only exits after the execution of this loop. How can I fix this?
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.progressbar import ProgressBar
class MyApp(App):
def build(self):
self.wid = FloatLayout()
self.prog_bar = ProgressBar(max=99999, pos = [0, -150])
self.wid.add_widget(self.prog_bar)
self.prog_bar.value = 0
for i in range(0, 99999):
self.prog_bar.value = i
print(i/99999)
return self.wid
if __name__ == '__main__':
MyApp().run()
Kivy uses the main thread of your App to update its widgets. You are running your loop on the main thread, so Kivy cannot update the ProgressBar until that loop completes. The fix is to do the loop in another thread:
class MyApp(App):
def build(self):
self.wid = FloatLayout()
self.prog_bar = ProgressBar(max=99999, pos=[0, -150])
self.wid.add_widget(self.prog_bar)
self.prog_bar.value = 0
threading.Thread(target=self.do_progress).start()
return self.wid
def do_progress(self):
for i in range(0, 99999):
self.prog_bar.value = i
print(i / 99999)
This is not about composition, but rather about multitasking, the thing is, if you do a locking loop like this, kivy can’t update anything until the loop is done (because nothing else happens while the function runs, no update of the UI or processing of any event). So what you want to do is allow the loop to run "concurrently" to the kivy event loop.
There are multiple ways to do this kind of things, they are all more or less suited to different situations.
kivy Clock allows scheduling a function to run later (schedule_once), possibly multiple times (schedule_interval), so you could have a more atomic function, that just increments the value of the progress bar once (not in a loop), and call this function using schedule interval. The issue is if you want to do actual work in the function, that could take significant enough time for the UI to be visibly blocked between increment of the progressbar value.
If that’s your case, because the function you are trying to run is slow, then you might want to run the function in a Thread instead, so it doesn’t block the kivy UI. You’ll be able to update the value of your progress bar from the the thread, if necessary, using the Clock to schedule a function doing just that (you need to be careful about not doing things that update the OpenGL context from a thread, as OpenGL doesn’t specify the behavior in such situation, so some graphic cards/drivers can be fine with it, but others will crash your program, so anything updating the UI from a thread must use the clock, there is a mainthread decorator provided in the clock module for that).
In some situation, you can (and thus want) avoid doing the long work beforehand, and find a way to do the minimal amount of work each time needed, and go on with the work, there are some abstractions of that idea, like RecycleView, that avoids creating widgets altogether, when you want a very long list of items to display, just creating the data beforehand, and letting it handle the creation/update of the widgets as needed. Of course, this might not apply to your situation, but it’s worth mentioning in case it is.
So in short: don’t do such loops, or do them in threads, think really hard if you actually need such a loop, and use clock events to update your UI when using threads.
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 have a Python script that performs some intensive processing of user's files and can take some time. I've build a user interface to it using Kivy, that allows the user to select the file, processing mode and shows them some messages as the process goes on.
My problem is that when the main Kivy loop passes calls the underlying user interface, the window freezes.
From what I've understood, the proper way of resolving this is to create a separate process to which the script would be off-loaded and from which it would send the updates to the user interface.
However, I was not able to find an example of how to do this or any specification on how to send messages from a separate thread back into application.
Could someone please give an example of how to do this properly or point me to the documentation pertaining to the subject?
Update:
For the sake of keeping the program maintainable I would like to avoid calling the elements of loops of processor from the main thread and instead call one long process that comes back to updated elements of the GUI, such as the progress bar or a text field. It looks like those elements can be modified only from the main kivy thread. How do I gain access to them from the outside?
Use publisher/consumer model as described here. Here's an example from that link modified to use separate threads:
from kivy.app import App
from kivy.clock import Clock, _default_time as time # ok, no better way to use the same clock as kivy, hmm
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.button import Button
from kivy.properties import ListProperty
from threading import Thread
from time import sleep
MAX_TIME = 1/60.
kv = '''
BoxLayout:
ScrollView:
GridLayout:
cols: 1
id: target
size_hint: 1, None
height: self.minimum_height
MyButton:
text: 'run'
<MyLabel#Label>:
size_hint_y: None
height: self.texture_size[1]
'''
class MyButton(Button):
def on_press(self, *args):
Thread(target=self.worker).start()
def worker(self):
sleep(5) # blocking operation
App.get_running_app().consommables.append("done")
class PubConApp(App):
consommables = ListProperty([])
def build(self):
Clock.schedule_interval(self.consume, 0)
return Builder.load_string(kv)
def consume(self, *args):
while self.consommables and time() < (Clock.get_time() + MAX_TIME):
item = self.consommables.pop(0) # i want the first one
label = Factory.MyLabel(text=item)
self.root.ids.target.add_widget(label)
if __name__ == '__main__':
PubConApp().run()
I think it's worth providing a 2022 update. Kivy apps can now be run via Python's builtin asyncio library and utilities. Previously, the problem was there was no way to return control to the main Kivy event loop when an async function call finished, hence you could not update the GUI. Now, Kivy runs in the same event loop as any other asyncio awaitables (relevant docs).
To run the app asynchronously, replace the YourAppClass().run() at the bottom of your main.py with this:
import asyncio
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(
YourAppClass().async_run()
)
loop.close()
And that's about it. With regards to the docs:
It is fully safe to interact with any kivy object from other
coroutines running within the same async event loop. This is because
they are all running from the same thread and the other coroutines are
only executed when Kivy is idling.
Similarly, the kivy callbacks may safely interact with objects from
other coroutines running in the same event loop. Normal single
threaded rules apply to both case.
If explicitly need to create a new thread, #Nykakin 's approach is what you want. I'd recommend using Queues to pass data between threads, instead, because they're simpler to implement and more robust, being specifically designed for this purpose. If you just want asynchronicity, async_run() is your best friend.
BE WARNED: While modifying a kivy property from another thread nominally works, there is every indication that this is not a thread safe operation. (Use a debugger and step through the append function in the background thread.) Altering a kivy property from another thread states that you should not modify a property in this way.
Trying to migrate from PyQt with Kivy and I cant even imagine a solution for this.
I have thousands of lines of code that use Qt's dialogues for text input. That is, when their line of code is reached, they 'stop' the script until the "ok" button is pressed, so they can return the text input.
Kivy doesnt have that functionality, so ideally, when the program needs user input, the "ok" button would call for the next function to run.
Therefore I must replace all the current calls to a PyQt function with a function that stops the running script, launches a working responsive dialogue, then resumes the original when it has the text input it requested. So the question is:
Is there a way to stop a running script until a function finishes, without hanging the GUI?
I have already tried:
Threading:
Even if I start the text input in a new thread:
t = threading.Thread(target=TextInput.waiter)
the function that calls such thread will return just after calling the text input.
If I use this code:
t.start()
t.join()
The main script will stop, but also hangs the text input GUI.
While/Sleep: Waiting for the text input variable to contain a valid result. But this blocks the ongoing textinput GUI in Kivy
Hacking raw_input:
Currently thinking into try some hack with that, that would allow me to stop the script, then feed back the input found by the kivy text input popup.
Any pointers would be really welcome, thanks for reading.
You can't just pause the running script. Instead, you'll need to refactor your program to be event-driven (as Kivy is an event-driven GUI).
Here's a simple example function:
def myfunc():
# do some stuff here
# now we need some input...
val = qt_input_dialogue()
# do some more stuff here
Refactored:
class MyPopup(Popup):
value = StringProperty() # bind this to a TextInput or something
def myfunc1():
# do some stuff here
p = MyPopupClass()
p.bind(on_dismiss=lambda *_: myfunc2(p.value))
p.open()
def myfunc2(val):
# do some more stuff here
If you're willing to use Twisted, you can make this even easier using Deferreds and inlineCallbacks.
from kivy.support import install_twisted_reactor
install_twisted_reactor()
from twisted.internet import defer
Builder.load_string('''
<MyPopup>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: text_input
BoxLayout:
orientation: 'horizontal'
Button:
text: 'OK'
on_press: root.okfn(text_input.text)
''')
class MyPopup(Popup):
def show(self, *args):
d = defer.Deferred()
self.okfn = d.callback
self.open(*args)
return d
#defer.inlineCallbacks
def myfunc():
# do some stuff here
val = yield MyPopup().show()
# do some more stuff here
This way, you can just replace the calls to QT input dialogues with yield MyPopup().show().