Slide 1

Slide 1 text

asyncio the exciting world of concurrent Python coroutines… Jacob Tomlinson

Slide 2

Slide 2 text

https://opsdroid.github.io

Slide 3

Slide 3 text

Concurrency and coroutines

Slide 4

Slide 4 text

“Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations” - Wikipedia "coroutines are functions whose execution you can pause"- Brett Cannon (Python core) -or-

Slide 5

Slide 5 text

Isn’t that like threading?

Slide 6

Slide 6 text

Common use cases ● Event driven applications ● IO bound applications ● Long running IO events (websockets) ● Applications which make lots of network requests (uploading/downloading data)

Slide 7

Slide 7 text

Once upon a time...

Slide 8

Slide 8 text

1. Generators

Slide 9

Slide 9 text

1.1 yield (PEP 255, Python 2.2)

Slide 10

Slide 10 text

1.1 Generators - yield def eager_range(up_to): """Create a list of integers, from 0 to up_to, exclusive.""" sequence = [] index = 0 while index < up_to: sequence.append(index) index += 1 return sequence

Slide 11

Slide 11 text

1.1 Generators - yield def lazy_range(up_to): """Generator to return the sequence of integers from 0 to up_to, exclusive.""" index = 0 while index < up_to: yield index index += 1

Slide 12

Slide 12 text

1.2 value = yield (PEP 342, Python 2.5)

Slide 13

Slide 13 text

1.2 Generators - value = yield def jumping_range(up_to): """Generator for the sequence of integers from 0 to up_to, exclusive. Sending a value into the generator will shift the sequence by that amount. """ index = 0 while index < up_to: jump = yield index if jump is None: jump = 1 index += jump if __name__ == '__main__': iterator = jumping_range(5) print(next(iterator)) # 0 print(iterator.send(2)) # 2 print(next(iterator)) # 3 print(iterator.send(-1)) # 2 for x in iterator: print(x) # 3, 4

Slide 14

Slide 14 text

1.3 yield from (PEP 380, Python 3.3)

Slide 15

Slide 15 text

1.3 Generators - yield from yield from iterator # was roughly equivalent to for x in iterator: yield x

Slide 16

Slide 16 text

1.3 Generators - yield from def bottom(): # Returning the yield lets the value that goes up the call stack to come right back # down. return (yield 42) def middle(): return (yield from bottom()) def top(): return (yield from middle()) # Get the generator. gen = top() value = next(gen) print(value) # Prints '42'.

Slide 17

Slide 17 text

2. asyncio (Python 3.4)

Slide 18

Slide 18 text

An event loop “is a programming construct that waits for and dispatches events or messages in a program” - Wikipedia

Slide 19

Slide 19 text

2.1 @asyncio.coroutine (Python 3.4)

Slide 20

Slide 20 text

2.1 asyncio - coroutine import time def countdown(number, n): while n > 0: print('T-minus', n, '({})'.format(number)) time.sleep(1) n -= 1 countdown("A", 2) countdown("B", 3) Output T-minus 2 (A) T-minus 1 (A) T-minus 3 (B) T-minus 2 (B) T-minus 1 (B) Time taken: 5s

Slide 21

Slide 21 text

Demo 2.1 asyncio - coroutine

Slide 22

Slide 22 text

2.1 asyncio - coroutine import asyncio @asyncio.coroutine def countdown(number, n): while n > 0: print('T-minus', n, '({})'.format(number)) yield from asyncio.sleep(1) n -= 1 loop = asyncio.get_event_loop() tasks = [ asyncio.ensure_future(countdown("A", 2)), asyncio.ensure_future(countdown("B", 3))] loop.run_until_complete(asyncio.wait(tasks)) loop.close() Output T-minus 2 (A) T-minus 3 (B) T-minus 1 (A) T-minus 2 (B) T-minus 1 (B) Time taken: 3s

Slide 23

Slide 23 text

Demo 2.1 asyncio - coroutine

Slide 24

Slide 24 text

2.2 Future and Task (Python 3.4)

Slide 25

Slide 25 text

4. aiohttp A Future is an object that represents the result of work that hasn’t completed. A Task is a wrapper for a coroutine and a subclass of Future.

Slide 26

Slide 26 text

Job 1 Job 2 Job 1.1 Job 2.1 Job 1.1.1

Slide 27

Slide 27 text

3. async and await

Slide 28

Slide 28 text

3. async and await # Python 3.4 @asyncio.coroutine def py34_coro(): yield from stuff() # Python 3.5 async def py35_coro(): await stuff()

Slide 29

Slide 29 text

4. Compatible libraries

Slide 30

Slide 30 text

4.1 aiohttp (PEP 3156) HTTP client/server for asyncio

Slide 31

Slide 31 text

4.1 aiohttp ● Supports both Client and HTTP Server. ● Supports both Server WebSockets and Client WebSockets out-of-the-box. ● Web-server has Middlewares, Signals and pluggable routing.

Slide 32

Slide 32 text

4.1.1 Libraries - aiohttp - client import aiohttp import asyncio import async_timeout async def fetch(session, url): with async_timeout.timeout(10): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html = await fetch(session, 'http://python.org') print(html) loop = asyncio.get_event_loop() loop.run_until_complete(main())

Slide 33

Slide 33 text

Demo 4.1.1 Libraries - aiohttp - client

Slide 34

Slide 34 text

4.1.2 Libraries - aiohttp - server from aiohttp import web async def handle(request): name = request.match_info.get('name', "Anonymous") text = "Hello, " + name return web.Response(text=text) app = web.Application() app.router.add_get('/', handle) app.router.add_get('/{name}', handle) web.run_app(app)

Slide 35

Slide 35 text

4.2 asynctest Unit tests for asyncio

Slide 36

Slide 36 text

4.3 database modules asyncpg, aiomysql, aioodbc, motor, asyncio-redis, aioes...

Slide 37

Slide 37 text

5. run_in_executor aka legacy support

Slide 38

Slide 38 text

5.1 run_in_executor - blocking import asyncio import time async def my_blocking_coroutine(): print("Blocking started") time.sleep(4) print("Blocking done") async def my_nonblocking_coroutine(): print("Nonblocking started") await asyncio.sleep(2) print("Nonblocking done") loop = asyncio.get_event_loop() tasks = [ asyncio.ensure_future(my_nonblocking_coroutine()), asyncio.ensure_future(my_blocking_coroutine())] loop.run_until_complete(asyncio.wait(tasks))

Slide 39

Slide 39 text

Demo 5.1 run_in_executor - blocking

Slide 40

Slide 40 text

5.2 run_in_executor - nonblocking import asyncio import time async def my_blocking_coroutine(): print("Blocking started") await loop.run_in_executor(None, time.sleep, 4) print("Blocking done") async def my_nonblocking_coroutine(): print("Nonblocking started") await asyncio.sleep(2) print("Nonblocking done") loop = asyncio.get_event_loop() tasks = [ asyncio.ensure_future(my_nonblocking_coroutine()), asyncio.ensure_future(my_blocking_coroutine())] loop.run_until_complete(asyncio.wait(tasks))

Slide 41

Slide 41 text

Demo 5.2 run_in_executor - blocking

Slide 42

Slide 42 text

There’s even more! but we don’t have time for that (you may have even lost the will to live by now) ● Queues ● Gathers ● Sockets ● Signals ● Pipes ● Transports ● Streams ● Subprocesses ● Exceptions ● Protocols ● more...

Slide 43

Slide 43 text

Further Reading http://www.snarky.ca/how-the-heck-does-async-await-work-in-python-3-5 https://docs.python.org/3/library/asyncio.html http://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/ https://carlosmaniero.github.io/asyncio-handle-blocking-functions.html https://github.com/dabeaz/curio