我写的所有代码都没有考虑异步;然而,我使用一个异步的函数(由另一个开发人员编写;出于我的目的,它是一个黑盒)。让我们称之为func_1
。我需要从另一个函数中调用这个函数,称之为func_2
(它本身可以在任意长的函数链func_3
、func_4
等中调用)
由于func_1
是异步的,我需要等待它,但由于我在func_2
中调用它,我也需要使func_2
异步(我不能在非异步函数中等待)。这是一个持续的过程;我需要将整个函数链func_2
、func_3
、func_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和运行很长的函数时要小心。