使用Python asyncio运行并等待来自同步功能的异步功能

时间:2019-03-13 17:30:08

标签: python-3.x asynchronous python-asyncio

在我的代码中,我有一个带有属性的类,有时需要运行异步代码。有时我需要从异步函数访问属性,有时需要从同步函数访问属性-这就是为什么我不希望属性是异步的。此外,我的印象是异步属性通常是代码的味道。如果我错了请纠正我。

从同步属性执行异步方法并阻止进一步执行,直到异步方法完成为止,我遇到了问题。

这是示例代码:

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

其输出:

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

首先,RuntimeError捕获似乎是错误的,但是如果我不这样做,我会得到RuntimeError: This event loop is already running异常。

第二,asynchronous()函数在同步完成一次后最后执行。我想通过异步方法对数据集进行一些处理,因此我需要等待它完成。 如果我在调用await asyncio.sleep(0)之后添加synchronous_property(),它将在asynchronous()完成之前调用main(),但这对我没有帮助。我需要在asynchronous()完成之前运行synchronous_property()

我想念什么?我正在运行python 3.7。

4 个答案:

答案 0 :(得分:1)

Asyncio确实坚持不允许嵌套循环by design。但是,您始终可以在其他线程中运行另一个事件循环。这是一种使用线程池来避免每次都必须创建新线程的变体:

import asyncio, concurrent.futures

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

pool = concurrent.futures.ThreadPoolExecutor()

def synchronous_property():
    print('entering synchronous_property')
    result = pool.submit(asyncio.run, asynchronous()).result()
    print('exiting synchronous_property', result)

async def asynchronous():
    print('entering asynchronous')
    await asyncio.sleep(1)
    print('exiting asynchronous')
    return 42

asyncio.run(main())

此代码在每个sync-> async边界上创建一个新的事件循环,因此,如果您经常这样做,不要指望高性能。可以通过使用asyncio.new_event_loop每个线程仅创建一个事件循环,并将其缓存在线程局部变量中来进行改进。

答案 1 :(得分:0)

  

我想进行异步调用以从同步执行并阻止其执行

只需使sync功能异步,然后等待异步功能即可。异步函数就像普通函数一样,您可以将所需的任何代码放入其中。如果仍然有问题,请尝试使用实际代码修改问题。

import asyncio


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


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

    await asynchronous()

    # Do whatever sync stuff you want who cares

    print('exiting synchronous_property')


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


asyncio.run(main())

答案 2 :(得分:0)

上述问题似乎有问题。重申问题: 如何在线程(不包含异步进程,因此不考虑同步)和异步过程(在某些事件循环中运行)之间进行通信。一种方法是使用两个同步队列。同步过程将其请求/参数放入QtoAsync中,然后等待QtoSync。异步进程读取QtoAsync,而无需等待,如果找到请求/参数,则执行请求,并将结果放入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()

这应该有效。

问题如下: 1.当使用asyncio.run(或类似程序)启动异步进程时,执行块将停止,直到异步进程完成。在调用asyncio.run之前,必须显式启动一个单独的同步线程。 2.通常,异步进程依赖于该循环中的其他异步进程。因此,不允许直接从另一个线程调用异步进程。交互应该与事件循环进行,使用两个队列是一种方法。

答案 3 :(得分:0)

最简单的方法是使用现有的“滚轮”, 喜欢 asgiref.async_to_sync

from asgiref.sync import async_to_sync

然后:

async_to_sync(main)()

通常:

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.