In order to prevent from context switching, I want to create a big loop to serve both the network connections and some routines.
Here's the implementation for normal functions:
import asyncio
import time
def hello_world(loop):
print('Hello World')
loop.call_later(1, hello_world, loop)
def good_evening(loop):
print('Good Evening')
loop.call_later(1, good_evening, loop)
print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
print('step: loop.call_soon(hello_world, loop)')
loop.call_soon(hello_world, loop)
print('step: loop.call_soon(good_evening, loop)')
loop.call_soon(good_evening, loop)
try:
# Blocking call interrupted by loop.stop()
print('step: loop.run_forever()')
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print('step: loop.close()')
loop.close()
Here's the implementation for coroutines:
import asyncio
@asyncio.coroutine
def hello_world():
while True:
yield from asyncio.sleep(1)
print('Hello World')
@asyncio.coroutine
def good_evening():
while True:
yield from asyncio.sleep(1)
print('Good Evening')
print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
print('step: loop.run_until_complete()')
loop.run_until_complete(asyncio.wait([
hello_world(),
good_evening()
]))
except KeyboardInterrupt:
pass
finally:
print('step: loop.close()')
loop.close()
And the mixed one:
import asyncio
import time
def hello_world(loop):
print('Hello World')
loop.call_later(1, hello_world, loop)
def good_evening(loop):
print('Good Evening')
loop.call_later(1, good_evening, loop)
@asyncio.coroutine
def hello_world_coroutine():
while True:
yield from asyncio.sleep(1)
print('Hello World Coroutine')
@asyncio.coroutine
def good_evening_coroutine():
while True:
yield from asyncio.sleep(1)
print('Good Evening Coroutine')
print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
print('step: loop.call_soon(hello_world, loop)')
loop.call_soon(hello_world, loop)
print('step: loop.call_soon(good_evening, loop)')
loop.call_soon(good_evening, loop)
print('step: asyncio.async(hello_world_coroutine)')
asyncio.async(hello_world_coroutine())
print('step: asyncio.async(good_evening_coroutine)')
asyncio.async(good_evening_coroutine())
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print('step: loop.close()')
loop.close()
As you see, each coroutine function has a while loop surrounded. How can I make it like the normal one? I.e. when it is done, call itself after the given delay time, but not just put a loop there.
If you really want to eliminate the while-loop from the coroutines (I'm not sure why you feel that's necessary; it's the most natural way to do what you're trying to do), you can use asyncio.async
(or asyncio.ensure_future
on Python 3.4.4+) to schedule the coroutine to run again on the next event loop iteration:
import asyncio
@asyncio.coroutine
def hello_world():
yield from asyncio.sleep(1)
print('Hello World')
asyncio.async(hello_world())
@asyncio.coroutine
def good_evening():
yield from asyncio.sleep(1)
print('Good Evening')
asyncio.async(good_evening())
print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
print('step: loop.run_until_complete()')
asyncio.async(hello_world())
asyncio.async(good_evening())
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print('step: loop.close()')
loop.close()
Note that you have to switch back to using loop.run_forever()
if you do this, since hello_world
/good_evening
will exit immediately after printing now.
# asyncio_coroutine_forever.py
import asyncio
async def hello_world():
await asyncio.sleep(1)
print('Hello World')
await good_evening()
async def good_evening():
await asyncio.sleep(1)
print('Good Evening')
await hello_world()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(hello_world())
loop.run_until_complete(good_evening())
loop.run_forever()
finally:
print('closing event loop')
loop.close()
import asyncio
@asyncio.coroutine
def hello_world_coroutine():
yield from asyncio.sleep(1)
print('Hello World Coroutine')
yield from hello_world_coroutine()
@asyncio.coroutine
def good_evening_coroutine():
yield from asyncio.sleep(1)
print('Good Evening Coroutine')
yield from good_evening_coroutine()
print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
print('step: loop.run_until_complete()')
loop.run_until_complete(asyncio.wait([
hello_world_coroutine(),
good_evening_coroutine()
]))
except KeyboardInterrupt:
pass
finally:
print('step: loop.close()')
loop.close()
UPD
This code would reach the maximum recursion depth. Might because Python has no tail call optimization. Leave the code here as a wrong example.
Did you actually try to run the three examples you gave? The difference in behaviour is pretty obvious.
Since you never said what you expect, there’s not telling what is right and what is not. All three implementations could be right or wrong. I can tell you what behaviour each implementation has, and why it has such behaviour; only you know whether it is correct.
In the second example (yield from asyncio.sleep(1)
), the two coroutines are run concurrently. This means that each will execute on their own; hello_world
prints Hello World
every second, and good_evening
prints Good Evening
every second.
The other two examples both use time.sleep(1)
, which is blocking. This means that when the first function (whichever that is; let’s say it’s hello_world
) reaches time.sleep(1)
, the whole program will hang for one second. This means that when hello_world
sleeps, good_evening
cannot run either, and vice versa.
The program executes like this:
- The loop is entered.
- The loop calls
hello_world
.
time.sleep(1)
in hello_world
is reached. The program sleeps for one second.
Hello World
printed.
hello_world
yields.
- The loop calls
good_evening
.
Good Evening
printed.
time.sleep(1)
in good_evening
is reached. The program sleeps for one second.
good_evening
yields.
- Go to 2.
Therefore both Hello World
and Good Evening
appear every two seconds, because there are two time.sleep(1)
calls between each print
. You would easily notice that if you run the two examples.