RuntimeError: 此事件循环已经在 python 中运行

我想我得到这个错误是因为我的代码调用 asyncio.get_event_loop().run_until_complete(foo())两次。一次来自 foo(),第二次来自 foo()调用的函数。那么我的问题是: 为什么这会成为一个问题?我为什么要关心这个循环是否正在运行?


这个问题有一个编辑,我认为,模糊了它(一些人喜欢遵循规则,而不理解他们,因此从标题中删除了一个“非法”字)。不幸的是,这造成了混乱。

对于错误被提出这一事实,我并不感到惊讶。我可以追溯到 asyncio的源代码,看到这个图书馆的作者想要这样做,没有什么神秘的。令人费解的部分在于,当循环已经在运行时,库的作者认为从事件循环请求运行某个函数直到完成是非法的。

我们可以将问题简化为两个这样的呼吁,通过案例分析,我们将看到这是三种可能性:

  1. 这两个函数都不会终止。
  2. 其中一个函数最终终止。
  3. 这两个函数最终都会终止。

那么,有没有什么正常的行为可以解决这三个案子呢?对我来说,很明显,这里可能存在,或者可能存在多种理智的行为。例如:

  1. 没有什么特别的,两个函数的执行是交叉的,并且它们一直在运行,正如预期的那样。
  2. 在第二个函数完成之前,循环不会将控制返回给 run_until_complete()的第一个实例之后的代码(因此在 run_until_complete()之后不会执行任何代码)。
  3. 在最后一个函数终止后,循环将控制返回给第一个调用 run_until_complete的代码对象,该代码对象忽略所有其他调用站点。

现在,我可以理解这种行为可能不是每个人都想要的。但是,由于这个库决定让程序员控制启动/停止事件循环,因此它也应该满足这种决定的后果。多次启动同一个循环是一个错误,这使得库代码无法这样做,从而降低了使用 asyncio的库的质量和有用性(例如,aiohttp就是这种情况)。

193657 次浏览

事件循环运行-是异步程序的入口点。它管理所有协同程序、任务和回调的运行。在运行时运行循环是没有意义的: 在某种程度上,这就像试图从同一个已经运行的作业执行器中运行作业执行器一样。

既然您有这个问题,我想您可能会误解异步是如何工作的。请阅读 这篇文章-它不大,并给出了一个很好的介绍。

更新:

在事件循环已经运行的情况下,添加多个要由事件循环运行的内容是绝对没有问题的。你只需要等待就可以做到:

await coro()  # add coro() to be run by event loop blocking flow here until coro() is finished

或者创造一个任务:

# python 3.7+
asyncio.create_task(coro())  # add coro() to be run by event loop without blocking flow here


# This works in all Python versions but is less readable
asyncio.ensure_future(coro())

正如您所看到的,您不需要调用事件循环的方法就可以让它运行某些东西。

事件循环的方法,如 run_foreverrun_until_completeーー通常只是启动事件循环的一种方法。

run_until_complete(foo())的意思是: “添加 foo()由事件循环运行,并运行事件循环本身,直到 foo()没有完成”。

我通过使用 nest _ sync 解决了这个问题

pip install nest-asyncio

并在我的文件中添加以下行。

import nest_asyncio
nest_asyncio.apply()
__import__('IPython').embed()

我写下这些不是为了讨好大家,而是为了解释我们如何处理这样的情况: 当事件循环运行时,仅仅将异步函数排队并同步等待其结果是不起作用的。

run_until_complete不是用来同步运行任意数量的异步函数,而是用来运行整个异步程序的主入口点。这个约束并不能立即从文档中看出来。

因为类似 aiohttp 这样的库会将它自己的入口点排队,以便作为服务器运行,并使用 run_until_completerun_forever阻塞循环的同步操作,所以事件循环将已经在运行,你不能在那个事件循环上运行独立的同步操作,并在那个线程中等待它的结果。

也就是说,如果您必须在同步上下文中将异步操作排队到正在运行的事件循环中,并像普通函数那样获得结果,那么这可能是不可能的。一种方法是传入一个异步操作完成后将调用的同步回调。这当然会降低事件循环的速度。另一种方法是将结果放入队列中,并在同步循环中等待结果,从而模拟事件循环。

处理这种情况的另一种方法是在正在使用的异步 http 库的启动和清理回调中执行代码。这里有一个关于如何实现这个目标的 样本

在开头添加这些代码

!pip install nest_asyncio
import nest_asyncio
nest_asyncio.apply()

使用 nest_asyncio对我不起作用,因为 aiohttp开始抱怨

  • RuntimeError: Timeout context manager should be used inside a task

相反,我决定把所有打到 asyncio.run的电话都换成打到这个 asyncio_run的电话:

def asyncio_run(future, as_task=True):
"""
A better implementation of `asyncio.run`.


:param future: A future or task or call of an async method.
:param as_task: Forces the future to be scheduled as task (needed for e.g. aiohttp).
"""


try:
loop = asyncio.get_running_loop()
except RuntimeError:  # no event loop running:
loop = asyncio.new_event_loop()
return loop.run_until_complete(_to_task(future, as_task, loop))
else:
nest_asyncio.apply(loop)
return asyncio.run(_to_task(future, as_task, loop))




def _to_task(future, as_task, loop):
if not as_task or isinstance(future, Task):
return future
return loop.create_task(future)

第二个目标是能够把 asyncio.run想象成来自 JS 世界的 promise.resolve,或者来自。网络世界。


编辑 : 在我们的产品中,在一个相关的问题中,我们删除了这段代码而支持 另一个答案

我有同样的问题,我修复这个问题使用 巢异步 异步

解决方案一

只需安装软件包:

pip install nest-asyncio

然后加上这些句子:

import nest_asyncio
nest_asyncio.apply()

解决方案二

如果它不能使用 nest _ syncio,那么试试 异步:

import asyncio
asyncio.set_event_loop(asyncio.new_event_loop())