web-dev-qa-db-fra.com

Équivalent de asyncio.Queues avec des "threads" de travail

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é.

34
Seth

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.

37
Andrew Svetlov

Voici ce que j'utilise en production, déplacé vers Gist: https://Gist.github.com/thehesiod/7081ab165b9a0d4de2e07d321cc2391d

5
amohr

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())
2
pfreixes