如何使用Python Asyncio在异步pgAPI上实现同步外观?

2022-03-25 00:00:00 python python-asyncio aiohttp asyncpg

问题描述

设想一个异步aiohttpWeb应用程序,该应用程序由通过asyncpg连接的PostgreSQL数据库支持,并且没有其他I/O。如何才能有一个托管应用程序逻辑的中间层,即而不是异步?(我知道我可以简单地使所有东西都异步--但是想象一下我的应用程序有大量的应用程序逻辑,仅由数据库I/O绑定,并且我不能触及它的所有内容)。

伪码:

async def handler(request):
    # call into layers over layers of application code, that simply emits SQL
    ...

def application_logic():
    ...
    # This doesn't work, obviously, as await is a syntax
    # error inside synchronous code.
    data = await asyncpg_conn.execute("SQL")
    ...
    # What I want is this:
    data = asyncpg_facade.execute("SQL")
    ...
如何在asyncpg上构建允许应用程序逻辑进行数据库调用的同步外观?在这种情况下,使用async.run()asyncio.run_coroutine_threadsafe()等浮动的食谱不起作用,因为我们来自一个已经异步的上下文。我假设这不是不可能的,因为原则上已经有一个事件循环可以运行asyncpg协程。

额外问题:使await内部同步成为语法错误的设计原理是什么?允许await来自任何源自协程的上下文不是很有用吗?这样我们就有了在功能构建块中分解应用程序的简单方法?

编辑额外的好处:除了Paul's very good answer之外,我对避免阻塞主线程的解决方案感兴趣(产生更多类似gevent的东西)。另请参阅我对保罗回答的评论.


解决方案

您需要创建在其中运行异步代码的辅助线程。您可以使用其自己的事件循环来初始化辅助线程,该事件循环将永远运行。通过调用run_coroutine_ThreadSafe()并对返回的对象调用result()来执行每个异步函数。这是concurrent.futures.Future的一个实例,它的result()方法直到协程的结果从第二个线程准备好之后才返回。

这样,您的主线程实际上就像调用同步函数一样调用每个异步函数。在每个函数调用完成之前,主线程不会继续。顺便说一下,同步函数是否在事件循环上下文中实际运行并不重要。

对result()的调用当然会挡路主线程的事件循环。如果您想要获得从同步代码运行异步函数的效果,这是无法避免的。

不用说,这是一件难看的事情,而且它暗示了错误的程序结构。但您正在尝试转换旧版程序,这可能会对此有所帮助。

import asyncio
import threading
from datetime import datetime

def main():
    def thr(loop):
        asyncio.set_event_loop(loop)
        loop.run_forever()
    
    loop = asyncio.new_event_loop()
    t = threading.Thread(target=thr, args=(loop, ), daemon=True)
    t.start()

    print("Hello", datetime.now())
    t1 = asyncio.run_coroutine_threadsafe(f1(1.0), loop).result()
    t2 = asyncio.run_coroutine_threadsafe(f1(2.0), loop).result()
    print(t1, t2)
 

if __name__ == "__main__":
    main()

>>> Hello 2021-10-26 20:37:00.454577
>>> Hello 1.0 2021-10-26 20:37:01.464127
>>> Hello 2.0 2021-10-26 20:37:03.468691
>>> 1.0 2.0

相关文章