Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call a coroutine without yielding the event loop

I may break the code up for readability reasons. So

async coro_top():
  print('top')
  print('1')
  # ... More asyncio code

  print('2')
  # ... More asyncio code

... into something like

async coro_top():
  print('top')
  await coro_1()
  await coro_2()

async coro_1()
  print('1')
  # ... More asyncio code

async coro_2()
  print('2')
  # ... More asyncio code

However, the extra awaits mean that these are not strictly equivalent

  • Another concurrent task can run code between print('top') and print('1'), so makes race conditions a touch more likely for certain algorithms.

  • There is (presumably) a slight overhead in yielding the event loop

So is there a way of calling a coroutine without yielding the event loop in order to avoid the above?

like image 516
Michal Charemza Avatar asked Apr 07 '19 07:04

Michal Charemza


1 Answers

The premise of the question is incorrect: contrary to what people tend to expect, an await doesn't automatically yield to the event loop. You can easily test that:

async def noop():
    pass

async def busy_loop(msg):
    while True:
        print(msg)
        await noop()

# keeps printing 'a', the event loop is stuck
asyncio.get_event_loop().run_until_complete(
    asyncio.gather(busy_loop('a'), busy_loop('b')))

Although busy_loop awaits the whole time, it still blocks the event loop, so that other tasks will not run, and even cancelling it is impossible. This is because the noop coroutine it awaits never suspends execution.

await some_coroutine() doesn't mean "schedule some_coroutine() and yield to the event loop, resuming when it completes". It means "start executing some_coroutine() and, if/when it chooses to suspend, suspend along", and assuming the former can lead to bugs.

In other words, the broken-up code really is equivalent to the code before the refactoring. The only way for another task to execute between print('top') and print('1') is for a new await to be added between them (one that actually suspends the coroutine), but the same can be said of the original code as well.

There is (presumably) a slight overhead in yielding the event loop

The overhead exists, but it is comparable to the overhead of a function call, not to the significantly larger overhead of running an iteration of the event loop.

like image 186
user4815162342 Avatar answered Sep 30 '22 02:09

user4815162342