Comment asyncio travail?
Avant de répondre à cette question, nous avons besoin de comprendre un peu la base de termes, ignorer ces si vous connaissez déjà l'un d'eux.
Les générateurs sont des objets qui nous permettent de suspendre l'exécution d'une fonction python. L'utilisateur organisée générateurs de mettre en œuvre en utilisant le mot-clé yield
. Par la création d'une fonction normale contenant de l' yield
mot-clé, nous nous tournons fonction dans un générateur:
>>> def test():
... yield 1
... yield 2
...
>>> gen = test()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Comme vous pouvez le voir, appelant next()
sur le générateur entraîne l'interprète de test de charge du châssis, et de retour à l' yield
ed valeur. Appelant next()
encore, provoquer le cadre de charger à nouveau dans l'interprète de la pile, et continuer sur yield
ing une autre valeur.
Par la troisième fois next()
est appelé, notre générateur était fini, et StopIteration
a été levée.
Communiquer avec un générateur
Moins connu des générateurs, est le fait que vous pouvez communiquer avec eux à l'aide de deux méthodes: send()
et throw()
.
>>> def test():
... val = yield 1
... print(val)
... yield 2
... yield 3
...
>>> gen = test()
>>> next(gen)
1
>>> gen.send("abc")
abc
2
>>> gen.throw(Exception())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in test
Exception
Sur appel de gen.send()
, la valeur est passée comme une valeur de retour à partir de l' yield
mot-clé.
gen.throw()
d'autre part, permet de lever des Exceptions à l'intérieur de groupes électrogènes, l'exception soulevée à la même place, yield
a été appelé.
Renvoi de valeurs à partir de générateurs de
Retour d'une valeur à partir d'un générateur, les résultats de la valeur d'être mis à l'intérieur de l' StopIteration
d'exception. On peut par la suite de récupérer la valeur de l'exception et de l'utiliser à notre besoin.
>>> def test():
... yield 1
... return "abc"
...
>>> gen = test()
>>> next(gen)
1
>>> try:
... next(gen)
... except StopIteration as exc:
... print(exc.value)
...
abc
Voici, un nouveau mot-clé: yield from
Python 3.4 est venu avec l'ajout d'un nouveau mot-clé: yield from
. Ce mot clé permet de le faire est de passer sur n'importe quel next()
, send()
et throw()
dans un intérieur plus imbriqués générateur. Si l'intérieur du générateur renvoie une valeur, c'est également la valeur de retour de l' yield from
:
>>> def inner():
... print((yield 2))
... return 3
...
>>> def outer():
... yield 1
... val = yield from inner()
... print(val)
... yield 4
...
>>> gen = outer()
>>> next(gen)
1
>>> next(gen)
2
>>> gen.send("abc")
abc
3
4
Mettre tous ensemble
Lors de l'introduction d'un nouveau mot-clé yield from
en Python 3.4, nous sommes désormais en mesure de créer des générateurs à l'intérieur des générateurs que, comme dans un tunnel, de transmettre les données en arrière à partir de la plus intérieur à l'extérieur-la plupart des générateurs. Cela a donné naissance à un nouveau sens pour les générateurs - coroutines.
Coroutines sont des fonctions qui peuvent être arrêté et repris tout en cours d'exécution. En Python, qu'ils sont définis à l'aide de l' async def
mot-clé. Beaucoup, comme les générateurs, ils utilisent leur propre forme d' yield from
qui await
. Avant d' async
et await
ont été introduites en Python 3.5, nous avons créé des coroutines exactement de la même façon générateurs ont été créés (avec yield from
au lieu de await
).
async def inner():
return 1
async def outer():
await inner()
Comme chaque itérateur ou générateur de mettre en œuvre l' __iter__()
méthode, mettre en œuvre des coroutines __await__()
, ce qui leur permet de continuer à chaque fois await coro
est appelé.
Il y a un joli diagramme de séquence à l'intérieur de l' Python docs que vous devriez vérifier.
Dans asyncio, en dehors de coroutine fonctions, nous avons 2 objets importants: les tâches et les contrats à terme.
Les contrats à terme sont des objets qui ont l' __await__()
méthode de mise en œuvre, et leur mission est de maintenir un certain état et le résultat. L'état peut être l'un des suivants:
- Dans l'ATTENTE - l'avenir n'a pas de résultat ou d'une exception visée.
- ANNULÉ - l'avenir a été annulés à l'aide d'
fut.cancel()
- TERMINÉ - l'avenir était fini, que ce soit par un résultat en utilisant
fut.set_result()
ou par une exception définie à l'aide de fut.set_exception()
Le résultat, comme vous l'avez deviné, peut être soit un objet Python, qui seront retournés, ou d'une exception qui peut être soulevée.
Une autre importante caractéristique de l' future
objets, c'est qu'ils contiennent une méthode appelée add_done_callback()
. Cette méthode permet des fonctions à être appelé dès que la tâche est terminée - si elle a soulevé une exception ou fini.
Tâche les objets sont à terme spécial, qui s'enroulent autour de coroutines, et de communiquer avec le plus intérieur, l'extérieur et la plupart des coroutines. Chaque fois qu'une coroutine await
s a l'avenir, l'avenir est passé tout le chemin du retour à la tâche (tout comme dans l' yield from
), et la tâche qu'il reçoit.
Ensuite, la tâche se lie lui-même à l'avenir. Elle le fait en appelant add_done_callback()
sur l'avenir. À partir de maintenant, si l'avenir ne sera jamais fait, soit par l'annulation, passé une exception ou le passé d'un objet Python en conséquence, la tâche de rappel sera appelé, et il va remonter jusqu'à l'existence.
Asyncio
Le final de la gravure question à laquelle nous devons répondre est - comment est le IO mis en œuvre?
Profondément à l'intérieur de asyncio, nous avons une boucle d'événements. Une boucle de tâches. La boucle d'événement de l'emploi est d'appeler les tâches à chaque fois qu'ils sont prêts et de coordonner tous les efforts dans une seule machine de travail.
L'OI partie de la boucle d'événement est construit sur un seul appel de fonction select
. Select est une fonction de blocage, mis en œuvre par le système d'exploitation dessous, qui permet d'attente sur des sockets pour les données entrantes ou sortantes. Sur les données en cours de réception, il se réveille et retourne les sockets qui a reçu les données, ou les sockets qui sont prêts pour l'écriture.
Lorsque vous essayez d'envoyer ou recevoir des données sur une socket par asyncio, ce qui se passe réellement en dessous, c'est que le socket est d'abord vérifié si il a pas de données qui peut être lu immédiatement envoyé. Si c'est .send()
de la mémoire tampon est pleine, ou l' .recv()
tampon est vide, le socket est inscrit à l' select
de la fonction (en ajoutant simplement à l'une des listes, rlist
pour recv
et wlist
pour send
) et la fonction appropriée await
s nouvellement créé, future
objet, lié à cette prise.
Lorsque toutes les tâches disponibles sont en attente pour l'avenir, la boucle d'événement appels select
et attend. Lorsque l'une des prises de courant entrant les données, ou c'est send
tampon drainé jusqu', asyncio vérifie pour l'avenir de l'objet lié à cette prise, et il définit à fait.
Maintenant, toute la magie se produit. L'avenir est réglé en fait, la tâche qui a ajouté de lui-même avant avec add_done_callback()
s'élève jusqu'à revenir à la vie, et des appels .send()
sur la coroutine qui reprend à l'intérieur de la plupart des coroutine (en raison de l' await
chaîne) et vous lisez le nouveau les données reçues à partir d'une proximité de la mémoire tampon, il a été renversé à.
La méthode de la chaîne de nouveau, dans le cas d' recv()
:
-
select.select
attend.
- Un prêt socket, avec les données retournées.
- Les données de la socket est déplacé dans une mémoire tampon.
-
future.set_result()
est appelé.
- La tâche qui a ajouté de lui-même avec
add_done_callback()
est maintenant réveillé.
- La tâche des appels
.send()
sur la coroutine qui va tout le chemin à l'intérieur de la plupart des coroutine et de veille.
- La lecture de données à partir de la mémoire tampon et est retourné à notre humble utilisateur.
En résumé, asyncio utilise le générateur de capacités, qui permettent la suspension et la reprise de fonctions. Il utilise yield from
des fonctionnalités qui permettent la transmission des données en arrière à partir de l'intérieur de la plupart générateur à l'extérieur-plus. Il utilise toutes ces afin de stopper l'exécution de la fonction tandis qu'il attend IO pour compléter (en utilisant le système d'exploitation select
de la fonction).
Et le meilleur de tous? Tandis qu'une fonction est en pause, une autre peut exécuter et entrelacé avec le tissu délicat qui est asyncio.