如何在 asyncio 中使用阻塞函数

Posted

技术标签:

【中文标题】如何在 asyncio 中使用阻塞函数【英文标题】:How to use blocking functions with asyncio 【发布时间】:2018-05-25 01:56:40 【问题描述】:

我在一个项目中使用 django ORM(在 django 之外)。我的工作流程是

    通过 django ORM 选择对象和 然后使用异步库将其发送到消息队列

问题是你不能在异步环境中调用阻塞函数,你不能在阻塞环境中使用 async/await。

我想出了两个解决方案:

    整个程序应该是异步的。并在需要时使用loop.run_in_executor 调用阻塞函数。

    整个程序应该是同步的。并使用asyncio.run()(Python 3.7) 调用所需的异步函数。

我无法决定哪种方法更好。

我知道以前有人问过similar question。我的问题是在尝试组合阻塞和非阻塞代码时是否有一般规则?

【问题讨论】:

【参考方案1】:

鉴于这两者之间的选择,我肯定会推荐方法 #1。

#2 的缺点是通过将 asyncio 调用拆分为单独的小事件循环运行,您会错过很多 asyncio 功能。例如,你不能创建一个“后台”任务,它的执行跨越了对asyncio.run() 的多次调用,而这种事情对于日志记录、监控或超时非常有用。 (使用asyncio.run 也可能是一个性能问题,因为它会在每次调用时创建一个全新的事件循环,但这可以通过切换到run_until_complete 来解决。)

但还有第三种选择:

创建一个单独的线程,它只执行loop.run_forever() 并等待被分配工作。程序的其余部分由普通的阻塞代码组成,这些代码可以使用asyncio.run_coroutine_threadsafe() 从 asyncio 请求一些东西。该功能不会阻塞;它立即返回一个concurrent.futures.Future,您可以传递它,其result() 方法会自动等待结果可用。它支持其他功能,例如使用 waitas_completed 迭代器等等待多个实例并行完成。

恕我直言,这种方法结合了问题中两个选项的最佳特征。它让阻塞代码真正阻塞,仍然允许等待事情发生、产生线程等,而不强制全面使用async defrun_in_executor。同时,可以使用 asyncio 最佳实践编写 asyncio 部分,并使用长时间运行的事件循环为整个程序提供服务。您只需要小心all 与应用程序其余部分的事件循环交互(甚至调用像loop.stop 这样简单的东西)就可以使用loop.call_soon_threadsafeasyncio.run_coroutine_threadsafe 完成。

【讨论】:

以上是关于如何在 asyncio 中使用阻塞函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在不阻塞的情况下发出请求(使用 asyncio)?

我正在使用 asyncio,但异步函数正在使用 await asyncio.sleep(5) 阻塞其他异步函数

使用 asyncio 的非阻塞 I/O

我如何在异步中使用请求?

如何在主异步循环内的同步子进程内运行多个异步循环?

《asyncio 系列》4. 如何并发运行多个任务(asyncio.gatherasyncio.as_completedasyncio.wait)