Kivy app stops responding in a while loop

938 views
Skip to first unread message

ITDSS

unread,
Dec 10, 2018, 9:10:29 AM12/10/18
to Kivy users support
Hello,

I have a problem. With my project I need to let something run continiously with supposedly a while loop, which is getting executed until 2 conditions are met.
What this causes though, is that my Kivy window stops responding until the while has stopped (so when the 2 conditions got met). I want to be able to use the window while the while loop is running. I don't know what I should change about it, I made a snippet of code to give an idea on what I want to do: 

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.lang import Builder


import kivy


test_file
= Builder.load_string("""
<Test>:
    canvas.before:
        Rectangle:
            pos: self.pos
            size: self.size


    Button:
        text: "
Test"
        size: 150, 50
        pos: 25, 251
        on_press: root.Test_Button()


    Button:
        text: "
Click while other is running"
        size: 250, 50
        pos: 250, 251
        on_press: root.Test_Button2()
"""
)


class Test(Widget):
   
def __init__(self):
       
super(Test, self).__init__()


   
def Test_Button(Widget):
        i
= 0
        j
= 0


       
while (i < 1000000 and j < 1000000):
            i
+= 1
            j
+= 2
           
print("Test")


   
def Test_Button2(Widget):
       
print("Hey! This is responding!")


class Buttons(Widget):
   
pass


class ActionApp(App):


   
def build(self):
       
return Test()
       
return Buttons()


if __name__ == "__main__":
   
ActionApp().run()

Basically; you click on the "Test" button, which will execute a script that will take a while to finish. Then I want to be able to just press "Click while other is running", which as the name suggests, I could click while the while loop is running. But this is not the case. The window stops responding after I have clicked the "Test" button, and finally responds again when the while loop has finished.
How will I be able to get it so I can still use the rest of my buttons, without the Kivy window not responding.

Greetings,

ITDSS

Andrew McLeod

unread,
Dec 10, 2018, 9:21:40 AM12/10/18
to kivy-...@googlegroups.com
In short - Kivy only runs when your app isn't. Consequently while Python is running through your code, Kivy doesn't run (since it runs in the 'main thread'), the events don't fire, the screen isn't updated etc. This is why in general you do everything through events and return execution to Kivy until the event fires etc, or schedule things to run after some time/at an interval (using Clock.schedule_once or Clock.schedule_interval).

If you have a long-running task, and you want to be able to use stuff in the background while it works, then I think you have to use some sort of threading/multiprocessing model. I'm using Python 3.6 so I use the concurrent.futures module with multithreading, which makes it fairly easy to fire off background tasks and to attach callbacks. You may need to decorate those callbacks with the Clock.mainthread decorator since all Kivy drawing instructions are supposed to happen on the main thread (or you could fire events). I think just changing properties is also OK.

Andrew

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To post to this group, send email to kivy-...@googlegroups.com.
Visit this group at https://groups.google.com/group/kivy-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/48e69799-ed16-428b-a314-55627db9f413%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

ITDSS

unread,
Dec 10, 2018, 9:24:26 AM12/10/18
to Kivy users support
Hello,

I am sorry, I don't really get the grasp of what you're saying.
Could you explain further, maybe with the help of some code? 

Greetings,

ITDSS

Op maandag 10 december 2018 15:21:40 UTC+1 schreef Andrew McLeod:

Andrew McLeod

unread,
Dec 10, 2018, 9:47:22 AM12/10/18
to kivy-...@googlegroups.com
Kivy works (roughly) like this:

You run:
YourApp.run()

which calls Kivy which will have something like:

# main Kivy loop
while True:
    do_Kivy_stuff()
    draw_all_the_things()
    if there_are_events():
        do_stuff()

So when someone has pressed a button, this generates an event. If you have connected this event to one of your functions, then do_stuff() will call your function:

my_button_has_been_pressed()

and when that returns, it returns to the main Kivy loop. This does the Kivy stuff, draws all the things, responds to user input etc. If your code my_button_has_been_pressed() takes a long time, then Kivy never gets a chance to do anything, such as check for events, respond to user input, draw the screen etc. In other words, your functions should never take very long to run (unless you don't mind the code freezing while it runs, which sometimes is OK).

The problem, of course, is if you do have a long-running bit of code. Then you _cannot_ run it in the main function, which needs to return in order to let Kivy get on with its thing. So you need to write your function like this - note that there are many different ways of doing threading, so this is definitely pseudocode not real Python (there is no 'magical_threading_call').

def my_button_has_been_pressed():
    print("I've been pressed!")
    magical_threading_call(do_the_slow_thing, callback=done_the_slow_thing)
    print('engaged magic')
    return

def do_the_slow_thing()
    print('slow thing code')
    <slow code>
    print('done the slow code')
    done_the_slow_thing(result)

@Clock.mainthread
def done_the_slow_thing(result, *args):
    print('using the result')
    <use the result on the main Kivy thread>

The magical_threading_call will NOT go into do_the_slow_thing. It will fire that off in a separate thread or process, and then _immediately_ return (before 'do_the_slow_thing' finishes). do_the_slow_thing will run in parallel in the background. Execution of the main thread will return to Kivy once my_button_has_been_pressed has returned.
You probably don't need the callback for simpler cases using threading. If you decorate it with Clock.mainthread, then calling done_the_slow_thing doesn't actually call it done_the_slow_thing, but schedules it for the next Kivy 'tick'.

The resultant output will look like this:

I've been pressed!
engaged magic / slow thing code (the order of these two will be a bit random)
(some time passes)
done the slow code
using the result

I do this with concurrent futures:

but you need Python 3 (I think - but then Python 3 isn't even the future any more, it's the present and increasingly the past).

In this you create a pool of thread workers:

import concurrent.futures
executor = concurrent.futures.ThreadPoolExecutor()

then when you need to fire off a task in a thread you create a Futures object:
future = exector.submit(do_the_slow_thing, arg1, arg2, ...)
You can attach a callback to the futures object to be called when the slow thing has finished:
future.add_done_callback(done_the_slow_thing))
The callback will be returned the futures object as the only argument; you can get the returned value from the do_the_slow_thing call using
my_result = future.result()
This will also raise any exception that occured in do_the_slow_thing, or you can get any exception using future.exception() - or raise it with raise future.exception().

(you can use functools.partial if you really need to pass some more arguments that are known when you make the future).
Last trick: if you need to decorate your callback to run on the mainthread for some reason you can just decorate it directly using Clock.mainthread...
future.add_done_callback(Clock.mainthread(done_the_slow_thing))

Andrew

Andrew McLeod

unread,
Dec 10, 2018, 9:49:25 AM12/10/18
to kivy-...@googlegroups.com
There are probably typos in that (I've already spotted one where I mistyped 'executor' as 'exector', so be warned...

Elliot Garbus

unread,
Dec 10, 2018, 9:51:34 AM12/10/18
to Kivy users support
You need to let the kivy run the loop.  A suggestion would be break your background task up into smaller pieces, and have kivy call it from a clock schedule at a regular interval (say every 1/10th of a second).
Look at Clock.schedule_interval()

I have written programs where I need to check a communications port, and use this method to check for new data.  A function to read the buffer until it is empty is scheduled every 1/10th of a second. 

If breaking up the background process into smaller chunks is not possible you will need to create a seperate background process.  Look at the subprocess module in the python standard library.

Reply all
Reply to author
Forward
0 new messages