web-dev-qa-db-ja.com

コルーチンをrun_in_executorで使用できないのはなぜですか?

コルーチンとマルチスレッドを使用してURLを要求するサービスを実行したい。ただし、エグゼキュータのワーカーにコルーチンを渡すことはできません。この問題の最小限の例については、以下のコードを参照してください。

import time
import asyncio
import concurrent.futures

EXECUTOR = concurrent.futures.ThreadPoolExecutor(max_workers=5)

async def async_request(loop):
    await asyncio.sleep(3)

def sync_request(_):
    time.sleep(3)

async def main(loop):
    futures = [loop.run_in_executor(EXECUTOR, async_request,loop) 
               for x in range(10)]

    await asyncio.wait(futures)

loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))

次のエラーが発生します。

Traceback (most recent call last):
  File "co_test.py", line 17, in <module>
    loop.run_until_complete(main(loop))
  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "co_test.py", line 10, in main
    futures = [loop.run_in_executor(EXECUTOR, req,loop) for x in range(10)]
  File "co_test.py", line 10, in <listcomp>
    futures = [loop.run_in_executor(EXECUTOR, req,loop) for x in range(10)]
  File "/usr/lib/python3.5/asyncio/base_events.py", line 541, in run_in_executor
    raise TypeError("coroutines cannot be used with run_in_executor()")
TypeError: coroutines cannot be used with run_in_executor()

私はsync_requestの代わりにasync_request関数を使用できることを知っています。この場合、ブロッキング関数を別のスレッドに送信することによってコルーチンを持つことになります。

また、イベントループでasync_requestを10回呼び出すことができることも知っています。以下のコードのようなもの:

loop = asyncio.get_event_loop()
futures = [async_request(loop) for i in range(10)]
loop.run_until_complete(asyncio.wait(futures))

ただし、この場合は単一のスレッドを使用します。

マルチスレッド内で機能するコルーチンを両方のシナリオでどのように使用できますか?コードを見るとわかるように、私はpoolasync_requestに渡しています(使用していません)。これは、ワーカーに未来を作ってそれをプールし、非同期で(ワーカーを解放して)結果を待ちます。

私がそうしたいのは、アプリケーションをスケーラブルにするためです。不要なステップですか?私は単にURLごとにスレッドを持っているべきですか?それはそれですか?何かのようなもの:

LEN = len(list_of_urls)
EXECUTOR = concurrent.futures.ThreadPoolExecutor(max_workers=LEN)

十分ですか?

8
zeh

コルーチンを実行するには、スレッドコンテキストで新しいイベントループを作成して設定する必要があります。

import asyncio
from concurrent.futures import ThreadPoolExecutor


def run(corofn, *args):
    loop = asyncio.new_event_loop()
    try:
        coro = corofn(*args)
        asyncio.set_event_loop(loop)
        return loop.run_until_complete(coro)
    finally:
        loop.close()


async def main():
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=5)
    futures = [
        loop.run_in_executor(executor, run, asyncio.sleep, 1, x)
        for x in range(10)]
    print(await asyncio.gather(*futures))
    # Prints: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
11
Vincent