Reputation: 1282
I have a list which I have fetched from a server and here is what I want to do
result = [1,7,0,0,2,4....]
def got_json(req, result):
for i in result:
if i is odd:
wait for 5 seconds and call my_function
now continue
else:
call my_function (now)
So basically I'm looking for more like a time.sleep()
but using time.sleep
just freezes the app, I just want to pause the execution of my for loop in got_json
method and not all other stuff which I suppose time.sleep
does.
I tried using Clock.schedule_once
using this code
class MyWidget(BoxLayout):
def onbuttonclick(self):
my_list = range(10)
for i in my_list:
if (i%2 == 0) :
Clock.schedule_once(self.my_callback, 5)
continue
print("Whatever")
def my_callback(self,dt):
print("called")
The output seems like it is indeed scheduling the function but its not stopping the execution of the for loop, which is what I want Output of above code
Whatever
Whatever
Whatever
Whatever
Whatever
called
called
called
called
called
Output I want to have
Whatever
**5 seconds**
called
Whatever
**5 seconds**
called
and so on...
How can I use the Clock
object to do what I want? Thanks
Upvotes: 2
Views: 3942
Reputation: 68
An alternative answer that may be easier to understand for beginners like myself (all respect to @Mikhail Gerasimov's answer which is over my head):
In Kivy, the Clock.schedule_once function when inside a loop schedules all the callbacks at nearly the same time. So in the code above if dt (the number of seconds to delay your callback function) is 5, all the callbacks from the loop schedule at nearly the same time in the loop, and then all start at the nearly the same time 5 seconds later.
What worked for me to get around this was to declare dt as 5 seconds before the loop, and then at the end of each iteration of the loop add an additional 5 seconds to dt. This way when all the callbacks are scheduled at the end of the loop, the first callback starts at 5 seconds, the next at 10 seconds, the next at 15, and so on.
Here is the code edited using this method (explanation of the 'partial' addition can be found in the Kivy docs--I'm using Kivy 2.2.0):
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from functools import partial
class MyWidget(BoxLayout):
def onbuttonclick(self):
my_list = range(10)
dt = 5 #declare dt here
for i in my_list:
if i == 0:
print('whatever')
if (i%2 == 0) :
Clock.schedule_once(partial(self.my_callback, self, 'whatever'), dt)
# partial added above to allow Clock.schedule_once to accept arguments in addtion to dt
dt = dt + 5 # add 5 seconds to dt to make the next callback start 5 seconds after the last
continue
def my_callback(self, whatever, dt):
print("called")
print(whatever)
Upvotes: 0
Reputation: 39546
This is interesting question. Using thread - is most universal solution for tasks like this in general. However, if we talking about this concrete case, you can use generator and it's yield points to return yourself control flow and resume execution later using Clock.schedule_once
:
from functools import wraps
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
def yield_to_sleep(func):
@wraps(func)
def wrapper(*args, **kwargs):
gen = func()
def next_step(*_):
try:
t = next(gen) # this executes 'func' before next yield and returns control to you
except StopIteration:
pass
else:
Clock.schedule_once(next_step, t) # having control you can resume func execution after some time
next_step()
return wrapper
@yield_to_sleep # use this decorator to cast 'yield' to non-blocking sleep
def test_function():
for i in range(10):
if (i % 2 == 0):
yield 5 # use yield to "sleep"
print('Called')
else:
print('Whatever')
class TestApp(App):
def build(self):
test_function()
return BoxLayout()
if __name__ == '__main__':
TestApp().run()
Upd:
def func():
yield 'STEP 1'
yield 'STEP 2'
gen = func()
print('Result of calling generator-function is generator object:', gen, '\n')
res1 = next(gen)
print('''Calling next() on generator object executes original
function to the moment of yield point and freeze it\'s state:\n''', res1, '\n')
print('''Look at line of code that prints this mesage:
It\'s not located inside func(),
but we were able to call it "in the middle" of executing func():
We see msg "STEP 1", but we don't see "STEP 2" yet.
This is what makes generators so cool: we can execute part of it,
do anything we want and resume execution later.
In "yield_to_sleep" this resuming delegated to Clock.schedule_once
making generator being executed after some time and without freezing anything.''', '\n')
res2 = next(gen)
print('Another next() and we on next yield point inside func():\n', res2, '\n')
try:
next(gen)
except StopIteration:
print('''If no yield points left, another next() call will finish func()
and raise StopIteration exception. It\'s not error it\'s just way
to say to outer code that generator is done.''', '\n')
Upvotes: 2
Reputation: 1902
Let on_json() start a thread with the code you have now in on_json be the body of the thread. Just use time.sleep() in the thread. So your new thread blocks when it needs to but your main app does not.
Upvotes: 1