【问题标题】:Call async function from sync function, while the synchronous function continues : Python从同步函数调用异步函数,而同步函数继续:Python
【发布时间】:2023-08-26 11:19:01
【问题描述】:

在阅读了AsyncIO 上的许多文档和文章后,我仍然找不到答案:异步运行函数(不使用线程)并确保调用此异步函数的函数继续执行执行。

伪代码:

async def functionAsync(p):
    #...
    #perform intensive calculations
    #...
    print ("Async loop done")

def functionNormal():
    p = ""
    functionAsync(p)
    return ("Main loop ended")

 print ("Start Code")
 print functionNormal()

预期输出:

Start code
Main loop ended
Async loop done

使用loop.run_until_complete 的搜索示例,但不会返回functionNormal() 的打印值,因为它本质上是阻塞的。

【问题讨论】:

  • 如果它应该“不使用线程”运行,您希望它如何工作?或者,您是说 实现 可以在后台使用线程,但您不想显式创建线程?
  • 是的,这就是我的意思。明确地我不想创建一个线程。如果它在引擎盖下完成它很好(据我所知,它可能不是,根据我的阅读并发并不总是意味着一个新线程。)
  • 并发并不总是意味着一个新线程如果您对所有代码都使用协程 (async def)。但是您的要求是同步功能与异步代码同时执行,这肯定需要多个线程或纤程。
  • 如果我没记错的话,异步代码也可以在新的事件循环中启动。 loop = asyncio.new_event_loop() ..是的,你是对的,同步代码应该继续运行并转到下一行代码,如示例所示。
  • new_event_loop 只分配一个事件循环。要在其中实际运行异步代码,您必须使用run_until_completerun_forever,它们会阻塞当前线程——因此您需要一个额外的线程来同时运行同步代码和异步代码。没有线程它永远不会工作。

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


【解决方案1】:

asyncio 不能在不使用线程的情况下“在后台”运行任意代码。正如 user4815162342 所述,asyncio 您运行的事件循环会阻塞主线程并管理协程的执行。

如果您想使用asyncio 并利用它,您应该将所有使用协程的函数重写为协程,直到主函数 - 程序的入口点。这个主协程通常传递给run_until_complete。这个little post 更详细地揭示了这个话题。


既然您对 Flask 感兴趣,请看一下 Quart:它是一个尝试根据 asyncio 实现 Flask API(尽可能多)的 Web 框架。这个项目存在的原因是纯 Flask 与 asyncio 不兼容。 Quart 是为了兼容而编写的。

如果您想继续使用纯 Flask,但有异步的东西,请查看 gevent。通过猴子补丁它可以使您的代码异步。虽然这个解决方案有其自身的问题(这就是为什么创建asyncio)。

【讨论】:

    【解决方案2】:

    也许有点晚了,但我遇到了类似的情况,我在 Flask 中使用 run_in_executor 解决了它:

    def work(p):
        # intensive work being done in the background
    
    
    def endpoint():
        p = ""
        loop = asyncio.get_event_loop()
        loop.run_in_executor(None, work, p)
    

    我不确定这有多安全,因为循环没有关闭。

    【讨论】:

    • 会试一试。谢谢你的回答
    【解决方案3】:

    假设同步函数在异步函数内部,您可以使用异常来解决它。 伪代码:

    class CustomError(Exception):
    pass
    
    
    async def main():
        def test_syn():
            time.sleep(2)
            # Start Async
            raise CustomError
        try:
            test_syn()
        except CustomError:
            await asyncio.sleep(2)
        
    

    【讨论】: