Disons que nous avons une fonction factice:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
Quelle est la différence entre:
import asyncio
coros = []
for i in range(5):
coros.append(foo(i))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
Et:
import asyncio
futures = []
for i in range(5):
futures.append(asyncio.ensure_future(foo(i)))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))
Remarque : l'exemple renvoie un résultat, mais ce n'est pas l'objet de la question. Lorsque la valeur de retour compte, utilisez à la gather()
place de wait()
.
Quelle que soit la valeur de retour, je recherche des éclaircissements sur ensure_future()
. wait(coros)
et les wait(futures)
deux exécutent les coroutines, alors quand et pourquoi une coroutine devrait-elle être enveloppée ensure_future
?
Fondamentalement, quelle est la bonne façon (tm) d'exécuter un tas d'opérations non bloquantes en utilisant Python 3.5 async
?
Pour un crédit supplémentaire, que faire si je souhaite regrouper les appels? Par exemple, je dois appeler some_remote_call(...)
1000 fois, mais je ne veux pas écraser le serveur Web / la base de données / etc. avec 1000 connexions simultanées. C'est faisable avec un thread ou un pool de processus, mais existe-t-il un moyen de le faire avecasyncio
?
Mise à jour 2020 (Python 3.7+) : n'utilisez pas ces extraits. Utilisez plutôt:
import asyncio
async def do_something_async():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(foo(i)))
await asyncio.gather(*tasks)
def do_something():
asyncio.run(do_something_async)
Pensez également à utiliser Trio , une alternative tierce robuste à asyncio.
la source
ensure_future()
? Et si j'ai besoin du résultat, ne puis-je pas simplement l'utiliserrun_until_complete(gather(coros))
?ensure_future
programme la coroutine à exécuter dans la boucle d'événements. Alors je dirais que oui, c'est obligatoire. Mais bien sûr, vous pouvez également planifier les coroutines en utilisant d'autres fonctions / méthodes. Oui, vous pouvez utilisergather()
- mais rassembler attendra que toutes les réponses soient collectées.gather
et encapsulezwait
les coroutines données en tant que tâches en utilisantensure_future
(voir les sources ici et ici ). Il ne sert donc à rien d'utiliserensure_future
au préalable, et cela n'a rien à voir avec l'obtention des résultats ou non.ensure_future
a unloop
argument, donc il n'y a aucune raison d'utiliserloop.create_task
overensure_future
. Etrun_in_executor
ne fonctionnera pas avec les coroutines, un sémaphore devrait être utilisé à la place.create_task
overensure_future
, voir la documentation . Citationcreate_task() (added in Python 3.7) is the preferable way for spawning new tasks.
Réponse simple
async def
) ne l'exécute PAS. Elle renvoie des objets coroutine, comme la fonction générateur renvoie des objets générateurs.await
récupère les valeurs des coroutines, ie "appelle" la coroutineeusure_future/create_task
programmez la coroutine pour qu'elle s'exécute sur la boucle d'événements lors de la prochaine itération (sans attendre la fin, comme un thread démon).Quelques exemples de code
Commençons par clarifier quelques termes:
async def
convient;Cas 1,
await
sur une coroutineNous créons deux coroutines,
await
une, et utilisonscreate_task
pour exécuter l'autre.vous obtiendrez le résultat:
Explique:
task1 a été exécutée directement, et task2 a été exécutée dans l'itération suivante.
Cas 2, céder le contrôle à la boucle d'événements
Si nous remplaçons la fonction principale, nous pouvons voir un résultat différent:
vous obtiendrez le résultat:
Explique:
Lors de l'appel
asyncio.sleep(1)
, le contrôle a été renvoyé à la boucle d'événements, et la boucle vérifie les tâches à exécuter, puis exécute la tâche créée parcreate_task
.Notez que nous appelons d'abord la fonction coroutine, mais pas
await
celle-ci, donc nous venons de créer une seule coroutine et ne la faisons pas fonctionner. Ensuite, nous appelons à nouveau la fonction coroutine et l'enveloppons dans uncreate_task
appel, creat_task planifiera en fait l'exécution de la coroutine lors de la prochaine itération. Donc, dans le résultat,create task
est exécuté avantawait
.En fait, le but ici est de redonner le contrôle à la boucle, vous pourriez utiliser
asyncio.sleep(0)
pour voir le même résultat.Sous la capuche
loop.create_task
appelle réellementasyncio.tasks.Task()
, qui appelleraloop.call_soon
. Etloop.call_soon
mettra la tâche en placeloop._ready
. Lors de chaque itération de la boucle, il vérifie tous les rappels dans loop._ready et l'exécute.asyncio.wait
,asyncio.ensure_future
et enasyncio.gather
fait appelerloop.create_task
directement ou indirectement.Notez également dans la documentation :
la source
await task2
appel pourrait être clarifié. Dans les deux exemples, l'appel loop.create_task () est ce qui planifie la tâche2 sur la boucle d'événements. Donc, dans les deux ex, vous pouvez supprimer leawait task2
et encore task2 sera finalement exécuté. Dans ex2, le comportement sera identique, carawait task2
je pense que c'est juste la planification de la tâche déjà terminée (qui ne s'exécutera pas une deuxième fois), alors que dans ex1, le comportement sera légèrement différent puisque task2 ne sera pas exécuté tant que main n'est pas terminée. Pour voir la différence, ajoutezprint("end of main")
à la fin de l'ex1 principalUn commentaire de Vincent lié à https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 , qui montre que cela
wait()
englobe les coroutinesensure_future()
pour vous!En d'autres termes, nous avons besoin d'un avenir, et les coroutines seront silencieusement transformées en eux.
Je mettrai à jour cette réponse lorsque je trouverai une explication définitive sur la façon de regrouper les coroutines / futures.
la source
c
,await c
équivaut àawait create_task(c)
?Tiré du BDFL [2013]
Tâches
Dans cet esprit,
ensure_future
cela a du sens comme nom pour créer une tâche puisque le résultat du futur sera calculé que vous l' attendiez ou non (tant que vous attendez quelque chose). Cela permet à la boucle d'événements de terminer votre tâche pendant que vous attendez d'autres choses. Notez qu'en Python 3.7create_task
est le moyen préféré d' assurer un avenir .Remarque: j'ai changé «rendement de» dans les diapositives de Guido pour «attendre» ici pour la modernité.
la source