web-dev-qa-db-fra.com

Comment puis-je encapsuler une fonction synchrone dans une coroutine asynchrone?

J'utilise aiohttp pour construire un serveur API qui envoie des requêtes TCP à un serveur séparé. Le module qui envoie les TCP = les demandes sont synchrones et une boîte noire à mes fins. Mon problème est donc que ces demandes bloquent l'intégralité de l'API. J'ai besoin d'un moyen d'envelopper les demandes de module dans une coroutine asynchrone qui ne bloquera pas le reste de l'API.

Donc, en utilisant simplement sleep comme exemple simple, existe-t-il un moyen d'envelopper en quelque sorte du code synchrone chronophage dans une coroutine non bloquante, quelque chose comme ceci:

async def sleep_async(delay):
    # After calling sleep, loop should be released until sleep is done
    yield sleep(delay)
    return 'I slept asynchronously'
28
Zac Delventhal

Finalement, j'ai trouvé une réponse dans ce fil . La méthode que je cherchais est run_in_executor . Cela permet à une fonction synchrone d'être exécutée de manière asynchrone sans bloquer une boucle d'événements.

Dans l'exemple sleep que j'ai publié ci-dessus, cela pourrait ressembler à ceci:

import asyncio
from time import sleep
from concurrent.futures import ProcessPoolExecutor

async def sleep_async(loop, delay):
    # Can set executor to None if a default has been set for loop
    await loop.run_in_executor(ProcessPoolExecutor(), sleep, delay)
    return 'I slept asynchronously'

Voir aussi la réponse suivante -> Comment appelons-nous une fonction normale où une coroutine est attendue?

28
Zac Delventhal

Vous pouvez utiliser un décorateur pour envelopper la version de synchronisation dans une version asynchrone.

import time
from functools import wraps, partial


def wrap(func):
    @wraps(func)
    async def run(*args, loop=None, executor=None, **kwargs):
        if loop is None:
            loop = asyncio.get_event_loop()
        pfunc = partial(func, *args, **kwargs)
        return await loop.run_in_executor(executor, pfunc)
    return run

@wrap
def sleep_async(delay):
    time.sleep(delay)
    return 'I slept asynchronously'

ou utilisez la bibliothèque aioify

% pip install aioify

puis

@aioify
def sleep_async(delay):
    pass
11
ospider

Je ne sais pas si c'est trop tard, mais vous pouvez également utiliser un décorateur pour faire votre fonction dans un fil. TOUTEFOIS, notez qu'il s'agira toujours d'un blocage non coopératif contrairement à async qui est un blocage coopératif.

def wrap(func):
    from concurrent.futures import ThreadPoolExecutor
    pool=ThreadPoolExecutor()
    @wraps(func)
    async def run(*args, loop=None, executor=None, **kwargs):
        if loop is None:
            loop = asyncio.get_event_loop()
        future=pool.submit(func, *args, **kwargs)
        return asyncio.wrap_future(future)
    return run
0
hpca01