web-dev-qa-db-ja.com

ワーカー「スレッド」を含むasyncio.Queuesと同等

asyncio を使用するようにスレッド化されたプログラムを移植する方法を理解しようとしています。いくつかの標準ライブラリ Queues を中心に同期するコードがたくさんあります。基本的には次のようになります。

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

1つのスレッドが値(ユーザー入力の可能性があります)を作成し、別のスレッドが値を使用して何かを行います。重要なのは、これらのスレッドは新しいデータが存在するまでアイドル状態であり、その時点でスレッドが起動して何かを行うことです。

このパターンをasyncioを使用して実装しようとしていますが、「実行」する方法がわかりません。

私の試みは多かれ少なかれこのように見えます(そして何もしません)。

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

コルーチンの使用ではなく、タスクでのラッピング、フューチャーを作成または返すようにするなどのバリエーションを試しました。

私はasyncioをどのように使用するべきかについて間違った考えを持っていると思い始めています(おそらく、このパターンは私が気付いていない別の方法で実装する必要があります)。どんなポインタでもいただければ幸いです。

34
Seth

はい、正確に。タスクはあなたの友達です:

_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_は、タスクの作成にも使用できます。

また、覚えておいてください:q.put()コルーチンなので、yield from q.put(value)を使用する必要があります。

[〜#〜]更新[〜#〜]

例では、asyncio.Task()/asyncio.async()から新しいブランドのAPI loop.create_task()およびasyncio.ensure_future()に切り替えられました。

37
Andrew Svetlov

これが私が本番で使用しているもので、Gistに移動しました: https://Gist.github.com/thehesiod/7081ab165b9a0d4de2e07d321cc2391d

5
amohr

少し後で、たぶんOTは、独立したコンシューマーであったため、複数のタスクからQueueから消費できることを覚えておいてください。

次のスニペットは、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