How to create an event loop with rolling coroutine

2019-02-01 08:59发布

问题:

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.

回答1:

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.



回答2:

# 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()


回答3:

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.



回答4:

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:

  1. The loop is entered.
  2. The loop calls hello_world.
  3. time.sleep(1) in hello_world is reached. The program sleeps for one second.
  4. Hello World printed.
  5. hello_world yields.
  6. The loop calls good_evening.
  7. Good Evening printed.
  8. time.sleep(1) in good_evening is reached. The program sleeps for one second.
  9. good_evening yields.
  10. 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.