asyncio IO Loop

Create an event loop (which automatically becomes the default event loop in the context).


In [1]:
import asyncio
loop = asyncio.get_event_loop()

Run a simple callback as soon as possible:


In [2]:
def hello_world():
    print('Hello World!')
    loop.stop()

loop.call_soon(hello_world)
loop.run_forever()


Hello World!

Coroutine Examples

Coroutines can be directly scheduled in the eventloop.


In [3]:
async def aprint(text):
    await asyncio.sleep(1)
    print(text)
    return 42

loop.run_until_complete(aprint('Hello world!'))


Hello world!
Out[3]:
42

You can use as many awaits as you like in a couroutine:


In [4]:
async def aprint_twice(text):
    await asyncio.sleep(1)
    print(text)    
    await asyncio.sleep(1)
    print(text + ' (once more)')
    return 42

loop.run_until_complete(aprint_twice('Hello world!'))


Hello world!
Hello world! (once more)
Out[4]:
42

All normal control structures can be used:


In [5]:
async def aprint_twice():
    for i in range(1, 7):
        await asyncio.sleep(0.5)
        if i % 2:
            print('even')
        else:
            print('uneven, waiting some more...')
            await asyncio.sleep(1)

loop.run_until_complete(aprint_twice())


even
uneven, waiting some more...
even
uneven, waiting some more...
even
uneven, waiting some more...

Exceptions work just like you would expect


In [6]:
async def raiser():
    await asyncio.sleep(1)
    raise ValueError()
    
async def catcher():
    try:
        await raiser()
    except ValueError:
        print('caught something')
        
loop.run_until_complete(catcher())


caught something

Multiple Coroutines can be combined and executed concurrently:


In [7]:
tasks = asyncio.gather(aprint('Task 1'), aprint('Task 2'))
loop.run_until_complete(tasks)


Task 2
Task 1
Out[7]:
[42, 42]

Note that this only took one second, not two!

Automatic Checks


In [8]:
async def remember_me():
    print('I started.')
    await aprint('Did I forget something?')
a = remember_me()

Note that nothing happens as long as the coroutine is not awaited. Even the synchronous print is not executed.


In [9]:
a = 42


/Users/niko/.virtualenvs/async/lib/python3.6/site-packages/ipykernel_launcher.py:1: RuntimeWarning: coroutine 'remember_me' was never awaited
  """Entry point for launching an IPython kernel.

Not awaiting a coroutine raises an error.

Awaiting a coroutine "later" is ok though.


In [10]:
a = aprint('Did I forget something?')
loop.run_until_complete(a)
del(a)


Did I forget something?

Awaiting something that is not awaitable raises an error.


In [11]:
async def fail():
    await aprint
    
loop.run_until_complete(fail())


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-5a324fa5a36c> in <module>()
      2     await aprint
      3 
----> 4 loop.run_until_complete(fail())

/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py in run_until_complete(self, future)
    465             raise RuntimeError('Event loop stopped before Future completed.')
    466 
--> 467         return future.result()
    468 
    469     def stop(self):

<ipython-input-11-5a324fa5a36c> in fail()
      1 async def fail():
----> 2     await aprint
      3 
      4 loop.run_until_complete(fail())

TypeError: object function can't be used in 'await' expression

Async for-loop

Prepare a simple MongoDB collection to show this feature.


In [12]:
from motor.motor_asyncio import AsyncIOMotorClient

collection = AsyncIOMotorClient().aiotest.test
loop.run_until_complete(collection.insert({'value': i} for i in range(10)))


Out[12]:
[ObjectId('59e7bd275a58ce05921ce279'),
 ObjectId('59e7bd275a58ce05921ce27a'),
 ObjectId('59e7bd275a58ce05921ce27b'),
 ObjectId('59e7bd275a58ce05921ce27c'),
 ObjectId('59e7bd275a58ce05921ce27d'),
 ObjectId('59e7bd275a58ce05921ce27e'),
 ObjectId('59e7bd275a58ce05921ce27f'),
 ObjectId('59e7bd275a58ce05921ce280'),
 ObjectId('59e7bd275a58ce05921ce281'),
 ObjectId('59e7bd275a58ce05921ce282')]

The async for-loop saves us the boilerplate code to await each next value. Note that it runs sequentially (i.e., the elements are fetched after each other).


In [13]:
async def f():
    async for doc in collection.find():
        print(doc)
        
loop.run_until_complete(f())


{'_id': ObjectId('59e7bd275a58ce05921ce279'), 'value': 0}
{'_id': ObjectId('59e7bd275a58ce05921ce27a'), 'value': 1}
{'_id': ObjectId('59e7bd275a58ce05921ce27b'), 'value': 2}
{'_id': ObjectId('59e7bd275a58ce05921ce27c'), 'value': 3}
{'_id': ObjectId('59e7bd275a58ce05921ce27d'), 'value': 4}
{'_id': ObjectId('59e7bd275a58ce05921ce27e'), 'value': 5}
{'_id': ObjectId('59e7bd275a58ce05921ce27f'), 'value': 6}
{'_id': ObjectId('59e7bd275a58ce05921ce280'), 'value': 7}
{'_id': ObjectId('59e7bd275a58ce05921ce281'), 'value': 8}
{'_id': ObjectId('59e7bd275a58ce05921ce282'), 'value': 9}

In [14]:
loop.run_until_complete(collection.drop())

Async Context Manager


In [15]:
class AsyncContextManager:
    async def __aenter__(self):
        await aprint('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await aprint('exiting context')

async def use_async_context():
    async with AsyncContextManager():
        print('Hello World!')
        
loop.run_until_complete(use_async_context())


entering context
Hello World!
exiting context

One example is using locks (even though this doesn't require async exiting).


In [16]:
lock = asyncio.Lock()

async def use_lock():
    async with lock:
        await asyncio.sleep(1)
        print('one after the other...')

tasks = asyncio.gather(use_lock(), use_lock())
loop.run_until_complete(tasks)


one after the other...
one after the other...
Out[16]:
[None, None]

Note that this took two seconds instead of one.