13.13. AsyncIO Event Loop

  • Async code can only run inside an event loop.

  • The event loop is the driver code that manages the cooperative multitasking.

  • You can create multiple threads and run different event loops in each of them.

  • Python will create a default event loop only in Main Thread

  • Python will not create an event loop automatically for you on any other than main thread by default, this is to prevent from having multiple event lops created explicitly

  • Event loop can execute only one callback (coroutine) at a time

  • Some callbacks (coroutines) can schedule themselves once again (trampoline)

  • Reactors

  • Proactors

For example, Django uses the main thread to wait for incoming requests, so we can't run an asyncio event loop there, but we can start a separate worker thread for our event loop [3].

An event loop runs in a thread (typically the main thread) and executes all callbacks and Tasks in its thread. While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task. [4]

../../_images/asyncio-eventloop-sync.png

Figure 13.13. Source: Michael Kennedy [1]

../../_images/asyncio-eventloop-async.png

Figure 13.14. Source: Michael Kennedy [1]

../../_images/asyncio-eventloop-uvloop-doc.png

Figure 13.15. Source: Michael Kennedy [1]

../../_images/asyncio-eventloop-uvloop-using.png

Figure 13.16. Source: Michael Kennedy [1]

13.13.1. Selectors

../../_images/asyncio-eventloop-selectors.png

Figure 13.17. Source: Langa, Ł. import asyncio: Learn Python's AsyncIO [2]

../../_images/asyncio-eventloop-selectors-unix.png

Figure 13.18. Source: Langa, Ł. import asyncio: Learn Python's AsyncIO [2]

13.13.2. Loop

import asyncio

loop = asyncio.new_event_loop()

Return the running event loop in the current OS thread.

If there is no running event loop a RuntimeError is raised. This function can only be called from a coroutine or a callback.


loop = asyncio.get_event_loop()
loop.run_forever()

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.sleep(3))

from datetime import datetime

def print_now():
     print(datetime.now())

loop.call_soon(print_now)
loop.call_soon(print_now)
loop.run_until_complete(asyncio.sleep(3))



def trampoline(name: str = '') -> None:
     print(name, end=' ')
     print_now()
     loop.call_later(0.5, trampoline, name)

loop.call_soon(trampoline)
loop.call_later(8, loop.stop)
loop.run_forever()

loop.call_soon(trampoline, 'First')
loop.call_soon(trampoline, 'Second')
loop.call_soon(trampoline, 'Third')
loop.call_later(8, loop.stop)
loop.run_forever()



# loop.run_until_complete(future)
#   Run until the future (an instance of Future) has completed.
#
# loop.run_forever()
#   Run the event loop until stop() is called.
#
# loop.stop()
#   Stop the event loop.
#
# loop.is_running()
#   Return True if the event loop is currently running.
#
# loop.is_closed()
#   Return True if the event loop was closed.
#
# loop.close()
#   Close the event loop.


# loop.call_soon(callback, *args, context=None)
#   Schedule the callback callback to be called with args arguments at the
# next iteration of the event loop. This method is not thread-safe.
#
# loop.call_soon_threadsafe(callback, *args, context=None)
#   A thread-safe variant of call_soon(). Must be used to schedule callbacks
# from another thread.


# loop.call_later(delay, callback, *args, context=None)
#   Schedule callback to be called after the given delay number of seconds (
# can be either an int or a float).
#
# loop.call_at(when, callback, *args, context=None)
#   Schedule callback to be called at the given absolute timestamp when (an
# int or a float), using the same time reference as loop.time().
#
# loop.time()
#   Return the current time, as a float value, according to the event loop's
# internal monotonic clock.

13.13.3. UVLoop

  • The ultimate loop implementation for UNIXes (run this on production)

$ pip install uvloop
>>> 
... import uvloop
...
... uvloop.install()
...
... loop = asyncio.new_event_loop()
... loop
<uvloop.Loop running=False closed=False debug=False>

13.13.4. References