J'essaie de rendre le code ci-dessous asynchrone:
async def count(): l = [] for i in range(10000000): l.append(i) choice = await random.choice(l) return choice
Actuellement, il fonctionnera de manière synchrone.
Est-ce parce que la fonction count
il manque l'instruction await
?
J'ai essayé de retravailler la fonction pour inclure await
:
import asyncio import random async def count(): l = [] for i in range(10000000): l.append(i) return random.choice(l) async def long_task1(): print('Starting task 1...') task_output = await count() print('Task 1 output is {}'.format(task_output )) async def long_task2(): print('Starting task 2...') task_output = await count() print('Task 2 output is {}'.format(task_output )) async def main(): await asyncio.gather(long_task1(), long_task2()) if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main())
et il démarrera de manière asynchrone (les deux Tâche de démarrage 1 ...
et Tâche de départ 2 ...
seront imprimés l'un après l'autre), mais j'obtiens une erreur:
TypeError: l'objet int ne peut pas être utilisé dans l'expression 'await'
Je comprends que l'erreur s'est produite parce que le résultat de random.choice (l)
n'est pas un attendu (une coroutine), mais je ne sais pas comment résoudre ce problème sans tourner en rond. Dois-je en quelque sorte refactoriser la boucle for en une couroutine seule?
3 Réponses :
Votre code appelle un rassembler
qui exécute simultanément long_task1
et long_task2
. Ensuite, vous appelez une attente sur count
dans chaque fonction. Mais cela attendra
sur ce sous-programme pour terminer. Ainsi, le sous-programme global se terminera toujours avant le début du sous-programme suivant. Vous avez besoin d'une fonction pour suspendre toute la tâche. J'ai créé deux façons de contourner cela. Les deux impliquent la création de nouvelles tâches.
Création d'un nouveau sous-programme:
async def long_task1(): print('Starting task 1...') task_output = await asyncio.shield(count()) print('Task 1 output is {}'.format(task_output )) async def long_task2(): print('Starting task 2...') task_output = await asyncio.shield(count()) print('Task 2 output is {}'.format(task_output ))
Vous pouvez également conserver votre fonction count
identique au code d'origine et modifier votre long_task (1/2)
comme ceci pour faire de count ()
une nouvelle tâche:
async def count(): l = [] await asyncio.wait_for(loopProcess(l), timeout=1000000.0) return random.choice(l) async def loopProcess(l): for i in range(10000000): l.append(i)
Vous pouvez également utiliser create_task
si vous avez python 3.7.
Source: https://docs.python.org /3/library/asyncio-task.html
Est-ce parce que la fonction de comptage ne dispose pas de l'instruction
wait
?
En bref, oui, vous avez correctement identifié le problème. Pour obtenir une exécution parallèle des tâches, vous devez non seulement spécifier async def
, mais aussi attendre quelque chose qui suspend l'exécution, renvoyant ainsi le contrôle à la boucle d'événements. En asyncio, c'est typiquement le type d'appel qui bloquerait un programme synchrone, comme un sommeil ou une lecture depuis une socket qui n'est pas encore prête pour la lecture.
Pour forcer une suspension temporaire , vous pouvez ajouter wait asyncio. sleep (0)
dans la boucle en count
. L'ajout de await
devant une fonction ordinaire telle que random.choice
, ne fonctionne pas car await
nécessite un objet qui implémente l'interface attendue, et dans votre code random.choice
renvoie simplement un entier.
Bonne réponse, dit-il explicitement sans utiliser sleep () cependant. Mais le point global est utile.
@IFunball Je n'ai pas vu ça, mais quand même, sleep (0)
est un peu spécial en ce qu'il ne dort pas réellement, il cède juste le contrôle à la boucle d'événements. Voir par exemple cet ancien problème github ou ce problème de doc . Si le code n'abandonne le contrôle de la boucle d'événements nulle part, il faut soit l'exécuter avec run_in_executor
soit insérer des points de rendement explicites (faux), il n'y a pas moyen de contourner cela.
Oui je suis d'accord. Je pense que c'est la meilleure solution. Je le dis seulement parce que j'ai utilisé le sommeil dans ma solution originale et ensuite il a dit qu'il ne voulait pas dormir. C'est l'un des commentaires sous le message d'origine.
@ user4815162342: Merci beaucoup! Encore deux questions: 1. Mettre await asyncio.sleep (0)
dans la boucle for rend le code beaucoup plus long que le synchrone d'origine, car - je suppose - le contrôle est renvoyé à le contrôleur à chaque itération de la boucle. Est-ce correct? 2. Si await nécessite un objet qui implémente l'interface attendue, et que dans votre code random.choice renvoie simplement un entier
, alors pourquoi les exemples de cette page: realpython.com/async-io-python en utilisant i = await randint (0, 10)
fonctionne?
@barciewicz Oui, vous devrez restructurer votre code pour produire moins souvent, ou passer à une fonction non asynchrone exécutée à l'aide de run_in_executor
. Puisque votre code ne résout pas un problème concret, on ne peut pas dire quelle serait la meilleure approche.
En ce qui concerne la deuxième question, le code lié s'exécute car il définit un async def randint
- mais cela n'accomplit rien, car il n'y a pas de wait
à l'intérieur. Si vous regardez attentivement, vous remarquerez que ses appelants attendent toujours quelque chose en plus de randint ()
, comme asyncio.sleep ()
ou randsleep ()
. Sans cela, l'auteur serait confronté au même problème que votre code.
Pour qu'asyncio fonctionne correctement, vous ne devriez pas avoir de tâche intensive de processeur (boucle serrée grande for) dans la boucle d'événements. Comme il n'y a aucun moyen de sortir de la boucle for. Si vous utilisez un asyncio.sleep
explicite dans la boucle, vous entrez et sortez simplement de la coroutine inutilement et ralentissez le tout. Si votre objectif est simplement de voir comment fonctionne asyncio, c'est bien.
Mais dans le monde réel, si vous avez une tâche intensive de processeur, vous avez deux options
Comme son nom l'indique, la bibliothèque est pour io asynchrone. async"io"
Je suppose que la boucle dans
count
est juste un exemple et non votre code réel?Eh bien, c'est un exercice que j'ai écrit pour moi-même pour comprendre l'asyncio. Donc, du code réel, je suppose: une simulation d'une tâche de longue durée.
@I Funball: oui, mais je ne veux pas du
asyncio.sleep
dans mon code, je veux la boucle for. Leasyncio.sleep
fonctionne car il est en attente. Je cherche un moyen de transformer la boucle for en une boucle attendue.@barciewicz Je n'ai pas vu cette spécification dans la question. J'essaierai alors de trouver une solution différente. Mais vous voulez la même sortie qui se produirait avec un sommeil?
@barciewicz voir mes modifications. J'ai ajouté un moyen de ne pas changer du tout votre fonction
count ()
ainsi que le moyen de refactoriser la boucle for en coroutine.@barciewicz
wait asyncio.sleep (0)
est b> le moyen de transformer une bouclefor
(ou tout code de longue durée dans uneasync def
) en une attente qui se suspend réellement. Si vous ne suspendez jamais, vous ne donnez pas à asyncio une chance de faire son travail. Le code asyncio normal a tendance à être orienté réseau, il n'a donc jamais besoin de le faire.