web-dev-qa-db-fra.com

Pourquoi les coroutines ne peuvent pas être utilisées avec run_in_executor?

Je veux exécuter un service qui demande des URL en utilisant des coroutines et du multithread. Cependant, je ne peux pas transmettre de coroutines aux travailleurs de l'exécuteur testamentaire. Consultez le code ci-dessous pour un exemple minimal de ce problème:

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

Il en résulte l'erreur suivante:

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

Je sais que je pourrais utiliser sync_request funcion au lieu de async_request, dans ce cas, j'aurais des coroutines en envoyant la fonction de blocage à un autre thread.

Je sais aussi que je pourrais appeler async_request dix fois dans la boucle d'événement. Quelque chose comme dans le code ci-dessous:

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

Mais dans ce cas, j'utiliserais un seul thread.

Comment pourrais-je utiliser les deux scénarios, les coroutines fonctionnant dans les multithreads? Comme vous pouvez le voir par le code, je passe (et n'utilise pas) le pool au async_request dans l'espoir que je puisse coder quelque chose qui dit au travailleur de faire un futur, l'envoyer au pool et de manière asynchrone (libérer le travailleur) attend le résultat.

La raison pour laquelle je veux le faire est de rendre l'application évolutive. Est-ce une étape inutile? Dois-je simplement avoir un thread par URL et c'est tout? Quelque chose comme:

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

est assez bon?

8
zeh

Vous devez créer et définir une nouvelle boucle d'événements dans le contexte du thread afin d'exécuter des coroutines:

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