如何在同步代码中调用异步函数并断开异步/等待链(即如何将异步函数封装在同步函数中)



我写的所有代码都没有考虑异步;然而,我使用一个异步的函数(由另一个开发人员编写;出于我的目的,它是一个黑盒)。让我们称之为func_1。我需要从另一个函数中调用这个函数,称之为func_2(它本身可以在任意长的函数链func_3func_4等中调用)

由于func_1是异步的,我需要等待它,但由于我在func_2中调用它,我也需要使func_2异步(我不能在非异步函数中等待)。这是一个持续的过程;我需要将整个函数链func_2func_3func_4转换为异步函数。

有办法避免这种情况吗?我只想调用func_1,等待它完成,并在我的普通python代码的其余部分中使用结果。我可以在func_1周围创建一个包装来允许这样做吗?

我想要的基本上是以下内容,但不起作用:

# This is the function defined by someone else
async def func_1(*args):
return something(*args)
# This is my wrapper
def func_1_wrapper(*args):
return await func_1(*args)
# So that I can call it like normal within the rest of my code
def func_2(*args):
# do something
a = func_1_wrapper(*args)
# do something else

本质上,您希望启动一个事件循环(异步代码的"引擎"),将函数提交到事件循环,然后等待事件循环执行完该函数。

一种方法是asyncio.run(func_1()),但如果代码中的其他内容已经启动了事件循环,或者在多线程上下文中运行,则可能会遇到问题。

处理这些边缘情况的一个简单方法是使用像asgiref.sync这样的库,它允许您执行:func_1_sync = async_to_sync(func_1),然后直接从同步函数调用func_1_sync()。以下是他们的pypi页面中的一个片段:

这些[helper函数]允许您包装或装饰要调用的异步或同步函数它们来自其他样式(因此您可以从同步线程或反之亦然)。

特别是:

AsyncToSync允许同步子线程停止并等待函数在主线程的事件循环上调用,然后控制在异步函数完成时返回给线程。SyncToAsync允许异步代码调用正在运行的同步函数在线程池中,当同步功能完成。这个想法是为了让打电话更容易来自异步代码的同步API和来自同步代码,因此更容易将代码从一种样式转换为另一个。

另一个这样做的库是syncer:

有时(主要在测试中)我们需要转换异步函数转换为正常的同步函数并同步运行它们。可以是由ayncio.get_event_loop().run_until_complete()完成,但它相当长…

Syncer使转换变得容易。

  • 将协程函数(由aync-def定义)转换为普通(同步)函数
  • 同步运行协同程序
  • 同时支持async def和decorator(@asyncio.coroutine)风格

如果您只想运行它,并且代码已经在后台运行了一个循环,请尝试next(coro.__await__())

我发布这篇文章是为了提供信息,不确定它是否能回答每个人的问题,其中一些可能看起来很明显,但这些信息帮助我理解了我的问题。

第一个解决方案

如果func_1是一个异步函数,您想在不等待的情况下使用它,您可以这样做:

def func_1_wrapper(*args):
return asyncio.run(func_1())

在较旧版本的asyncio中,将asyncio.run替换为asyncio.get_event_loop().run_until_complete(func_1())

请注意,这是其他人在给我的评论问题(@Ajax1234和@deceze)中所写内容的摘要。

这将在jupyter笔记本中失败

上述解决方案在jupyter中不起作用。原因是jupyter本身在您的过程中运行一个事件循环。有两件事你可以在jupyter

使所有东西都异步到顶级函数调用

在jupyter单元中,实际上可以直接等待异步函数(await func_1)。因此,如果你对链中需要使用async的每个函数都满意,那么你可以在笔记本中使用await来运行。

使用nestrongyncio

如果你不喜欢让所有东西都异步,也不喜欢在笔记本电脑中使用await运行函数,那么你可以这样做:

import nest_asyncio
nest_asyncio.apply()
def func_1_wrapper(*args):
asyncio.get_event_loop().run_until_complete(func_1(*args))

通常asyncio不允许嵌套的事件循环(即jupyter使用的事件循环不能启动新的事件循环,因此您可以.get_event_loop().run_until_complete()),但nest_asyncio.apply";补丁";以某种方式允许你这样做。

请注意,关于你是否应该这样做以及这样做是否安全,存在争议,但人们似乎无论如何都在使用它。

  • 一些关于jupyter中异步和事件循环的有趣讨论:https://discuss.python.org/t/supporting-asyncio-get-event-loop-run-until-complete-in-repls/5573
  • 不使用nest_asyncio的一些原因:https://github.com/erdewit/nest_asyncio/issues/36

使用nestrongyncio和运行很长的函数时要小心。

最新更新