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