web-dev-qa-db-fra.com

Exécutez et attendez la fonction asynchrone à partir d'une fonction synchrone en utilisant Python asyncio

Dans mon code, j'ai une classe avec des propriétés, qui ont parfois besoin d'exécuter du code asynchrone. Parfois, j'ai besoin d'accéder à la propriété à partir de la fonction asynchrone, parfois à partir de la synchronisation - c'est pourquoi je ne veux pas que mes propriétés soient asynchrones. D'ailleurs, j'ai l'impression que les propriétés asynchrones en général sont une odeur de code. Corrige moi si je me trompe.

J'ai un problème avec l'exécution de la méthode asynchrone à partir de la propriété synchrone et le blocage de l'exécution supplémentaire jusqu'à la fin de la méthode asynchrone.

Voici un exemple de code:

import asyncio


async def main():
    print('entering main')
    synchronous_property()
    print('exiting main')


def synchronous_property():
    print('entering synchronous_property')
    loop = asyncio.get_event_loop()
    try:
        # this will raise an exception, so I catch it and ignore
        loop.run_until_complete(asynchronous())
    except RuntimeError:
        pass
    print('exiting synchronous_property')


async def asynchronous():
    print('entering asynchronous')
    print('exiting asynchronous')


asyncio.run(main())

Sa sortie:

entering main
entering synchronous_property
exiting synchronous_property
exiting main
entering asynchronous
exiting asynchronous

Tout d'abord, la capture de RuntimeError semble incorrecte, mais si je ne le fais pas, j'obtiendrai l'exception RuntimeError: This event loop is already running.

Deuxièmement, la fonction asynchronous() est exécutée en dernier, après la fin synchrone. Je veux effectuer un traitement sur l'ensemble de données par une méthode asynchrone, je dois donc attendre qu'il se termine. Si j'ajoute await asyncio.sleep(0) après avoir appelé synchronous_property(), il appellera asynchronous() avant que main() termine, mais cela ne m'aide pas. J'ai besoin d'exécuter asynchronous() avant que synchronous_property() termine.

Qu'est-ce que je rate? J'exécute python 3.7.

4
Andrzej Klajnert

Il semble y avoir un problème avec la question comme indiqué. Reformulation de la question: comment communiquer entre un thread (ne contenant aucun processus asynchrone et donc considéré comme sync) et un processus asynchrone (fonctionnant dans une boucle d'événement). Une approche consiste à utiliser deux files d'attente de synchronisation. Le processus de synchronisation place sa demande/ses paramètres dans le QtoAsync et attend sur le QtoSync. Le processus asynchrone lit QtoAsync SANS attente, et s'il trouve une requête/des paramètres, exécute la requête et place le résultat dans QtoSync.

import queue
QtoAsync = queue.Queue()
QtoSync = queue.Queue()
...

async def asyncProc():
    while True:
        try:
            data=QtoAsync.get_nowait()
            result = await <the async that you wish to execute>
            QtoAsync.put(result) #This can block if queue is full. you can use put_nowait and handle the exception.
        except queue.Empty:
            await asyncio.sleep(0.001) #put a nominal delay forcing this to wait in event loop
....
#start the sync process in a different thread here..
asyncio.run(main()) #main invokes the async tasks including the asyncProc

The sync thread puts it request to async using:
req = <the async that you wish to execute>
QtoAsync.put(req)
result = QtoSync.get()

Cela devrait fonctionner.

Problème avec la question comme indiqué: 1. Lorsque les processus asynchrones sont démarrés avec des blocs d'exécution asyncio.run (ou similaire) jusqu'à la fin des processus asynchrones. Un thread de synchronisation distinct doit être démarré explicitement avant d'appeler asyncio.run 2. En général, les processus asyncio dépendent d'autres processus asyncio dans cette boucle. Ainsi, l'appel d'un processus asynchrone à partir d'un autre thread n'est pas autorisé directement. L'interaction doit être avec la boucle d'événements, et l'utilisation de deux files d'attente est une approche.

0
giwyni

La manière la plus simple est d'utiliser une "roue" existante, comme asgiref.async_to_sync

from asgiref.sync import async_to_sync

puis:

async_to_sync(main)()

en général:

async_to_sync(<your_async_func>)(<.. arguments for async function ..>)
This is a caller class which turns an awaitable that only works on the thread with
the event loop into a synchronous callable that works in a subthread.

If the call stack contains an async loop, the code runs there.
Otherwise, the code runs in a new loop in a new thread.

Either way, this thread then pauses and waits to run any thread_sensitive
code called from further down the call stack using SyncToAsync, before
finally exiting once the async task returns.
0
Sławomir Lenart