13.2. AsyncIO About

  • asyncio in Python standard library

  • async and await builtin keywords

  • Running asynchronously: 3s + 1s + 1s = bit over 3s [execution time]

  • Async is the future of programming

13.2.1. Advantages

  • Maximize the usage of a single thread

  • Handling I/O asynchronously

  • Enabling concurrent code using coroutines

  • Async will fill the gaps, otherwise wasted on waiting for I/O

  • You control when tasks switches occur, so locks and other synchronization are no longer needed

  • Async is the cheapest way to task switch

  • Cost task switches is incredibly low; calling a pure Python function has more overhead than restarting a generator or awaitable

  • Function builds stack each time it's called, whereas async uses generators underneath, which already has stack created

  • In terms of speed async servers blows threaded servers in means of thousands

  • Async is very cheap in means of resources

  • Async world has a huge ecosystem of support tools

  • Coding is easier to get right, than threads

13.2.2. Disadvantages

  • Async switches cooperatively, so you do need to add explicit code yield or await to cause a task to switch

  • Everything you do need a non-blocking version (for example open())

  • Increased learning curve

  • Create event loop, acquire, crate non-blocking versions of your code

  • You think you know Python, there is a second half to learn (async)

13.2.3. Sync vs Async

../../_images/asyncio-sequence-sync.png

Figure 13.8. Source: Michael Kennedy [1]

../../_images/asyncio-sequence-async.png

Figure 13.9. Source: Michael Kennedy [1]

../../_images/concurrency-sync-vs-async-1.png

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

../../_images/concurrency-sync-vs-async-2.png

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

13.2.4. Execution

../../_images/asyncio-execution-sync.png

Figure 13.12. Source: Michael Kennedy [1]

../../_images/asyncio-execution-async.png

Figure 13.13. Source: Michael Kennedy [1]

13.2.5. Example

>>> import asyncio
>>>
>>>
>>> async def a():
...     print('a: started')
...     await asyncio.sleep(0.2)
...     print('a: finished')
...     return 'a'
>>>
>>> async def b():
...     print('b: started')
...     await asyncio.sleep(0.1)
...     print('b: finished')
...     return 'b'
>>>
>>> async def c():
...     print('c: started')
...     await asyncio.sleep(0.3)
...     print('c: finished')
...     return 'c'
>>>
>>> async def main():
...     result = await asyncio.gather(a(), b(), c())
...     print(f'Result: {result}')
>>>
>>>
>>> asyncio.run(main())
a: started
b: started
c: started
b: finished
a: finished
c: finished
Result: ['a', 'b', 'c']

13.2.6. Further Reading

  • Kennedy, M. Demystifying Python's Async and Await Keywords [1]

  • Kennedy, M. Async Techniques and Examples in Python [2]

  • Abdalla, A. Creating a Bittorrent Client using Asyncio [3]

  • Langa, Ł. import asyncio: Learn Python's AsyncIO [4]

13.2.7. References

13.2.8. Assignments

Code 13.32. Solution
"""
* Assignment: OOP Async Coroutine
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min

English:
    1. Define:
        a. coroutine function `a()`
    2. After running coroutine should:
        b. return 'a'
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj:
        a. coroutine function `a()`
    2. Po uruchomieniu coroutine powinna:
        a. zwracać 'a'
    3. Uruchom doctesty - wszystkie muszą się powieść

    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import iscoroutine, iscoroutinefunction
    >>> import asyncio

    >>> assert iscoroutinefunction(a)
    >>> assert iscoroutine(a())

    >>> asyncio.run(a())
    'a'
"""


# Coroutine function `a()`
# Function should return 'a'
# type: Coroutine
def a():
    ...

Code 13.33. Solution
"""
* Assignment: OOP Async Sleep
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Define:
        a. coroutine function `a()`
    2. After running coroutine should:
        a. wait for 1.0 seconds
        b. return 'a'
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj:
        a. coroutine function `a()`
    2. Po uruchomieniu coroutine powinna:
        a. czekać 1.0 sekundę
        b. zwracać 'a'
    3. Uruchom doctesty - wszystkie muszą się powieść

    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import iscoroutine, iscoroutinefunction
    >>> import asyncio

    >>> assert iscoroutinefunction(a)
    >>> assert iscoroutine(a())

    >>> asyncio.run(a())
    'a'
"""

import asyncio

# coroutine function `a()`
# wait for 1.0 seconds, return 'a'
# type: Coroutine
def a():
    ...

Code 13.34. Solution
"""
* Assignment: OOP Async Concurrent
* Complexity: easy
* Lines of code: 15 lines
* Time: 5 min

English:
    1. Define:
        a. coroutine function `a()`
        b. coroutine function `b()`
        c. coroutine function `c()`
    2. After running coroutine should:
        a. print 'NAME: before'
        b. wait for X seconds
        c. print 'NAME: after'
        d. return NAME, where
    3. Definition of NAME:
        a. for coroutine `a()`, NAME is 'a'
        a. for coroutine `b()`, NAME is 'b'
        a. for coroutine `c()`, NAME is 'c'
    4. Definition of X:
        a. for coroutine `a()`, X is 1.0
        a. for coroutine `b()`, X is 0.5
        a. for coroutine `c()`, X is 1.5
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj:
        a. coroutine function `a()`
        b. coroutine function `b()`
        c. coroutine function `c()`
    2. Po uruchomieniu coroutine powinna:
        a. wyświetlić 'NAME: before'
        b. czekać X sekund
        c. wyświetlić 'NAME: after'
        d. zwrócić NAME
    3. Definicja NAME:
        a. dla coroutine `a()`, NAME to 'a'
        a. dla coroutine `b()`, NAME to 'b'
        a. dla coroutine `c()`, NAME to 'c'
    3. Definicja X:
        a. dla coroutine `a()`, X to 1.0
        a. dla coroutine `b()`, X to 0.5
        a. dla coroutine `c()`, X to 1.5
    3. Uruchom doctesty - wszystkie muszą się powieść

    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import iscoroutine, iscoroutinefunction
    >>> import asyncio

    >>> assert iscoroutinefunction(a)
    >>> assert iscoroutinefunction(b)
    >>> assert iscoroutinefunction(c)
    >>> assert iscoroutine(a())
    >>> assert iscoroutine(b())
    >>> assert iscoroutine(c())

    >>> async def main():
    ...     return await asyncio.gather(a(), b(), c())
    >>>
    >>> asyncio.run(main())
    a: before
    b: before
    c: before
    b: after
    a: after
    c: after
    ['a', 'b', 'c']
"""

import asyncio

# coroutine function `a()`
# print 'a: before', wait 1.0 second, print 'a: after', return 'a'
# type: Coroutine
def a():
    ...

# coroutine function `b()`
# print 'b: before', wait 0.5 second, print 'b: after', return 'b'
# type: Coroutine
def b():
    ...

# coroutine function `c()`
# print 'c: before', wait 1.5 second, print 'c: after', return 'c'
# type: Coroutine
def c():
    ...