J'essaie de comprendre comment porter un programme threadé pour utiliser asyncio
. J'ai beaucoup de code qui se synchronise autour de quelques bibliothèques standard Queues
, essentiellement comme ceci:
import queue, random, threading, time
q = queue.Queue()
def produce():
while True:
time.sleep(0.5 + random.random()) # sleep for .5 - 1.5 seconds
q.put(random.random())
def consume():
while True:
value = q.get(block=True)
print("Consumed", value)
threading.Thread(target=produce).start()
threading.Thread(target=consume).start()
Un thread crée des valeurs (éventuellement une entrée utilisateur) et un autre thread fait quelque chose avec elles. Le fait est que ces threads sont inactifs jusqu'à ce qu'il y ait de nouvelles données, auquel cas ils se réveillent et en font quelque chose.
J'essaie d'implémenter ce modèle en utilisant asyncio, mais je n'arrive pas à comprendre comment le faire "aller".
Mes tentatives ressemblent plus ou moins à cela (et ne font rien du tout).
import asyncio, random
q = asyncio.Queue()
@asyncio.coroutine
def produce():
while True:
q.put(random.random())
yield from asyncio.sleep(0.5 + random.random())
@asyncio.coroutine
def consume():
while True:
value = yield from q.get()
print("Consumed", value)
# do something here to start the coroutines. asyncio.Task()?
loop = asyncio.get_event_loop()
loop.run_forever()
J'ai essayé des variantes sur l'utilisation des coroutines, ne pas les utiliser, encapsuler des choses dans les tâches, essayer de les faire créer ou renvoyer des futures, etc.
Je commence à penser que j'ai une mauvaise idée de la façon dont je devrais utiliser asyncio (peut-être que ce modèle devrait être implémenté d'une manière différente que je ne connais pas). Tout pointeur serait apprécié.
Oui, exactement. Les tâches sont vos amis:
import asyncio, random
q = asyncio.Queue()
@asyncio.coroutine
def produce():
while True:
yield from q.put(random.random())
yield from asyncio.sleep(0.5 + random.random())
@asyncio.coroutine
def consume():
while True:
value = yield from q.get()
print("Consumed", value)
loop = asyncio.get_event_loop()
loop.create_task(produce())
loop.create_task(consume())
loop.run_forever()
asyncio.ensure_future
Peut également être utilisé pour la création de tâches.
Et n'oubliez pas: q.put()
est un coroutine, vous devez donc utiliser yield from q.put(value)
.
UPD
Passé de asyncio.Task()
/asyncio.async()
à la nouvelle API de marque loop.create_task()
et asyncio.ensure_future()
par exemple.
Voici ce que j'utilise en production, déplacé vers Gist: https://Gist.github.com/thehesiod/7081ab165b9a0d4de2e07d321cc2391d
Un peu plus tard et peut-être OT, gardez à l'esprit que vous pouvez consommer à partir du Queue
de plusieurs tâches car ils étaient des consommateurs indépendants.
L'extrait de code suivant montre à titre d'exemple comment vous pouvez obtenir le même modèle de pool de threads avec les tâches asyncio
.
q = asyncio.Queue()
async def sum(x):
await asyncio.sleep(0.1) # simulates asynchronously
return x
async def consumer(i):
print("Consumer {} started".format(i))
while True:
f, x = await q.get()
print("Consumer {} procesing {}".format(i, x))
r = await sum(x)
f.set_result(r)
async def producer():
consumers = [asyncio.ensure_future(consumer(i)) for i in range(5)]
loop = asyncio.get_event_loop()
tasks = [(asyncio.Future(), x) for x in range(10)]
for task in tasks:
await q.put(task)
# wait until all futures are completed
results = await asyncio.gather(*[f for f, _ in tasks])
assert results == [r for _, r in tasks]
# destroy tasks
for c in consumers:
c.cancel()
asyncio.get_event_loop().run_until_complete(producer())