# asyncio (ou pas)

## Julien Palard

 - Software Engineer
 - cPython core dev
 - Coordinator of docs.python.org/fr/
 - Python trainer
 - hackinscience.org


# Coroutine

Une coroutine est une fonction dont l'exécution peut être suspendue.


## Callback Hell

```
function pong_handler(client)
{
    client.on('data', function (data)
    {
        client.on('data_written', function ()
        {
            client.close()
        });
        client.write(data)
        client.flush()
    });
}
```


## Avec des coroutines

```python
async def pong_handler():
    client.write(await client.read())
    await client.flush()
    client.close()
```

## Les coroutines

Pour être exhaustif, il existe deux types de coroutines en Python :

 - generator-based coroutines
 - native coroutines


## Generator-based coroutines

```pytho
import types


@types.coroutine
def get_then_print(url):
    ...
```


## Native coroutines

```python
async def get_then_print(url):
    ...
```


## Coroutines

Une `coroutine`, renvoie un objet `coroutine` :

```
>>> async def tum():
...     print("tum")
...
>>> tum()
<coroutine object tum at 0x7fa294538468>
```


## Coroutines

Qu'on peut manipuler :


```
>>> async def tum():
...     print("tum")
...
>>> a_coroutine_object = tum()
>>> a_coroutine_object.send(None)
tum
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  StopIteration
```

Notes:

As you can see, calling `tum()` did not execute the `print("tum")`,
but calling `.send(None)` did (see PEP 342).

L'appel de .send est fait par la main loop (asyncio.run).


## Coroutines

Une coroutine renvoie juste un objet coroutine, donc à un moment, du code devra appeler la méthode `send` dessus.

C'est le rôle d'une *main loop*.


## Récupérer un résultat

Le résultat d'une coroutine est stocké dans l'exception `StopIteration`.

Notes:

Dans l'attribut `value`.


## await

Il existe un mot clé dédié à la récupération de résultat d'une coroutine :

`await`


## await

Lorsqu'on lui donne un *awaitable*, `await` essaye d'obtenir son résultat.

Si l'*awaitable* se suspend, `await` suspends à son tour la coroutine actuelle, récursivemet, jusqu'à l'appel à `send`.


## await


```
async def two():
    return 2

async def four():
    return await two() + await two()

coro = four()
coro.send(None)
```

Ce qui donne `StopIteration: 4`, de manière complètement synchrone, malgré le vocabulaire utilisé.


## Suspendre une coroutine.

Ce n'est pas possible dans une coroutine.

Mais toute la chaîne d'*await* peut-être suspendue en utilisant un
`yield` depuis un *future-like object*.


## Future-like object

Un `future-like object` est un object implémentant `__await__`, qui a
le droit de `yield`. L'expression du yield traversera toute la stack
d'`await` jusqu’au `send`.


## Awaitables

Les [awaitables](https://www.python.org/dev/peps/pep-0492/#await-expression)
sont des objets pouvant être « attendus » via un `await`.

Donc c'est soit une `coroutine` soit un objet implémentant `__await__`.


# Gérer ses coroutines

Appelons simplement `send`, et attendons-nous à la fameuse `StopIteration` :

```python
async def two():
    return 2

async def four():
    return await two() + await two()

def coro_manager(coro):
    try:
        coro.send(None)
    except StopIteration as stop:
        return stop.value

print(coro_manager(four()))
```


## Gérer ses coroutines

Mais si la coroutine se suspend, notre `coro_manager` ne suffira pas :

```python
class Awaitable:
    def __await__(self):
        yield

async def wont_terminate_here():
    await Awaitable()
    print("Terminated")
    return 42

print(coro_manager(wont_terminate_here()))
```

## Gérer ses coroutines

Donc, un gestionnaire de coroutines doit rappeler send, comme celui-ci :

```python
def frenetic_coro_manager(coro):
    try:
        while True:
            coro.send(None)
    except StopIteration as stop:
        return stop.value
```

## Gérer ses coroutines

Mais notre manager frénétique ne sait s'occuper que d'une seule
routine, peut-on faire mieux ?


## Gérer ses coroutines

On pourrait relancer le travail de l'une d'entre elles au hasard :

```python
import random


def frenetic_coros_manager(*coros):
    coros = list(coros)
    while coros:
        coro = random.choice(coros)
        try:
            coro.send(None)
        except StopIteration as stop:
            coros.remove(coro)
```


## Gérer ses coroutines

Essayons :

```python
async def tum():
    while True:
        await Awaitable()
        print("Tum")

async def pak():
    while True:
        await Awaitable()
        print("Pak")

frenetic_coros_manager(tum(), pak())
```

Ohh, deux `while True:` qui coopèrent !


# Questions?

- mamot.fr/@mdk
- github.com/JulienPalard
- mdk on #python-fr on libera.chat
