web-dev-qa-db-ja.com

Python-asyncioを使用して複数のコルーチンを同時に実行する方法?

websockets ライブラリを使用して、Python 3.4。のwebsocketサーバーを作成します。これは単純なエコーサーバーです。

import asyncio
import websockets

@asyncio.coroutine
def connection_handler(websocket, path):
    while True:
        msg = yield from websocket.recv()
        if msg is None:  # connection lost
            break
        yield from websocket.send(msg)

start_server = websockets.serve(connection_handler, 'localhost', 8000)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

さらに、何らかのイベントが発生するたびにクライアントにメッセージを送信したいとします。簡単にするために、60秒ごとに定期的にメッセージを送信しましょう。どうすればいいですか?つまり、connection_handlerは常に受信メッセージを待機しています。サーバーはアクションしか実行できませんafterクライアントからメッセージを受信しましたよね?ここに何が欠けていますか?

たぶん、このシナリオには、コルーチンに基づくものではなく、イベント/コールバックに基づくフレームワークが必要ですか? 竜巻

23
weatherfrog

TL; DRasyncio.ensure_future() を使用して、複数のコルーチンを同時に実行します。


たぶん、このシナリオには、コルーチンに基づくものではなく、イベント/コールバックに基づくフレームワークが必要ですか?竜巻?

いいえ、これには他のフレームワークは必要ありません。非同期アプリケーションと同期アプリケーションの全体的な考えは、結果を待つ間、ブロックしないということです。コルーチンまたはコールバックを使用して、どのように実装されるかは関係ありません。

つまり、connection_handlerは着信メッセージを常に待機しているため、サーバーはクライアントからメッセージを受信した後にのみアクションを実行できます。ここに何が欠けていますか?

同期アプリケーションでは、(説明したように)メッセージを受信するまでアプリケーション全体をブロックするmsg = websocket.recv()のようなものを記述します。ただし、非同期アプリケーションではまったく異なります。

msg = yield from websocket.recv()を実行すると、次のようになります。connection_handler()が何かを生成するまで、websocket.recv()の実行を一時停止します。コルーチン内で_yield from_を使用すると、制御がイベントループに戻されるため、websocket.recv()の結果を待っている間に他のコードを実行できます。コルーチンがどのように機能するかをよりよく理解するには、 documentation を参照してください。

さらに、何らかのイベントが発生するたびにクライアントにメッセージを送信したいとします。簡単にするために、60秒ごとに定期的にメッセージを送信しましょう。どうすればいいですか?

asyncio.async() を使用して、 starting event loop のブロック呼び出しを実行する前に、必要な数のコルーチンを実行できます。

_import asyncio

import websockets

# here we'll store all active connections to use for sending periodic messages
connections = []


@asyncio.coroutine
def connection_handler(connection, path):
    connections.append(connection)  # add connection to pool
    while True:
        msg = yield from connection.recv()
        if msg is None:  # connection lost
            connections.remove(connection)  # remove connection from pool, when client disconnects
            break
        else:
            print('< {}'.format(msg))
        yield from connection.send(msg)
        print('> {}'.format(msg))


@asyncio.coroutine
def send_periodically():
    while True:
        yield from asyncio.sleep(5)  # switch to other code and continue execution in 5 seconds
        for connection in connections:
            print('> Periodic event happened.')
            yield from connection.send('Periodic event happened.')  # send message to each connected client


start_server = websockets.serve(connection_handler, 'localhost', 8000)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.async(send_periodically())  # before blocking call we schedule our coroutine for sending periodic messages
asyncio.get_event_loop().run_forever()
_

クライアント実装の例を次に示します。名前の入力を求め、エコーサーバーから名前を受信し、サーバーからさらに2つのメッセージ(これは定期的なメッセージです)を待機し、接続を閉じます。

_import asyncio

import websockets


@asyncio.coroutine
def hello():
    connection = yield from websockets.connect('ws://localhost:8000/')
    name = input("What's your name? ")
    yield from connection.send(name)
    print("> {}".format(name))
    for _ in range(3):
        msg = yield from connection.recv()
        print("< {}".format(msg))

    yield from connection.close()


asyncio.get_event_loop().run_until_complete(hello())
_

重要なポイント:

  1. In Python 3.4.4 asyncio.async()は、 asyncio.ensure_future() に名前が変更されました。
  2. 遅延呼び出し をスケジュールするための特別な方法がありますが、コルーチンでは機能しません。
28
Yaroslav Admin

同じ問題ですが、ここで完璧なサンプルを見るまでほとんど解決策を得ることができません: http://websockets.readthedocs.io/en/stable/intro.html#both

 done, pending = await asyncio.wait(
        [listener_task, producer_task],
        return_when=asyncio.FIRST_COMPLETED)  # Important

そのため、ハートビートやredisサブスクライブなどの複数のコルーチンタスクを処理できます。

7
gzerone

gatherが言及されていないことに驚いています。

Pythonドキュメント から:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    # Schedule three calls *concurrently*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())

# Expected output:
#
#     Task A: Compute factorial(2)...
#     Task B: Compute factorial(2)...
#     Task C: Compute factorial(2)...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(3)...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4)...
#     Task C: factorial(4) = 24
0
Cyril N.