无法从正在运行的事件循环中调用asyncio.run()

Cha*_*han 7 python-3.x python-asyncio jupyter-notebook

我想使用asyncio获取网页html。

我在jupyter笔记本中运行以下代码:

import aiofiles
import aiohttp
from aiohttp import ClientSession

async def get_info(url, session):
    resp = await session.request(method="GET", url=url)
    resp.raise_for_status()
    html = await resp.text(encoding='GB18030')
    with open('test_asyncio.html', 'w', encoding='utf-8-sig') as f:
        f.write(html)
    return html

async def main(urls):
    async with ClientSession() as session:
        tasks = [get_info(url, session) for url in urls]
        return await asyncio.gather(*tasks)

if __name__ == "__main__":
    url = ['http://huanyuntianxiazh.fang.com/house/1010123799/housedetail.htm', 'http://zhaoshangyonghefu010.fang.com/house/1010126863/housedetail.htm']
    result = asyncio.run(main(url))
Run Code Online (Sandbox Code Playgroud)

但是,它返回 RuntimeError: asyncio.run() cannot be called from a running event loop

问题是什么?

怎么解决呢?

非常感谢你。

Mar*_*ark 27

结合 Pankaj Sharma 和 Jean Monet 的方法,我编写了以下代码片段,它充当 asyncio.run(语法略有不同),但也可以在 Jupyter 笔记本中运行。

import threading

class RunThread(threading.Thread):
    def __init__(self, func, args, kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.result = None
        super().__init__()

    def run(self):
        self.result = asyncio.run(self.func(*self.args, **self.kwargs))

def run_async(func, *args, **kwargs):
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = None
    if loop and loop.is_running():
        thread = RunThread(func, args, kwargs)
        thread.start()
        thread.join()
        return thread.result
    else:
        return asyncio.run(func(*args, **kwargs))
Run Code Online (Sandbox Code Playgroud)

用法:

async def test(name):
    await asyncio.sleep(5)
    return f"hello {name}"

run_async(test, "user")  # blocks for 5 seconds and returns "hello user"
Run Code Online (Sandbox Code Playgroud)


cgl*_*cet 17

asyncio.run()文档说:

当另一个异步事件循环在同一线程中运行时,无法调用此函数。

您遇到的问题是jupyter(IPython)已经在运行事件循环(对于IPython 7.0):

现在,您可以在IPython终端和笔记本电脑的顶层使用async / await了,“应该可以正常工作”。将IPython更新到版本7+,将IPykernel更新到版本5+,您就可以开始比赛了。

这就是为什么您不需要在jupyter中自己启动事件循环并可以直接调用的原因await main(url)

在朱庇特

async def main():
    print(1)

await main()
Run Code Online (Sandbox Code Playgroud)

在纯Python(?3.7)中

import asyncio

async def main():
    print(1)

asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)

在您的代码中将给出:

if __name__ == "__main__":
    url = ['url1', 'url2']
    result = await main(url)
    for text in result:
        pass # text contains your html (text) response
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,当我在 jupyter 中运行上述内容时,我得到: SyntaxError: 'await' external function (7认同)
  • 有没有办法让代码片段在 Jupyter 内部和外部都可以工作? (3认同)

Jea*_*net 12

要添加到cglacet的答案 - 如果有人想检测循环是否正在运行并自动调整(即main()在现有循环上运行,否则asyncio.run()),这是我尝试过的一项建议(如果确实有人想这样做):

try:
    loop = asyncio.get_running_loop()
except RuntimeError:  # if cleanup: 'RuntimeError: There is no current event loop..'
    loop = None

if loop and loop.is_running():
    print('Async event loop already running')
    tsk = loop.create_task(main())
    # ^-- https://docs.python.org/3/library/asyncio-task.html#task-object
    tsk.add_done_callback(                                          # optional
        lambda t: print(f'Task done: '                              # optional
                        f'{t.result()=} << return val of main()'))  # optional (using py38)
else:
    print('Starting new event loop')
    asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)


vgo*_*ani 8

只需使用这个:

https://github.com/erdewit/nest_asyncio

import nest_asyncio
nest_asyncio.apply()
Run Code Online (Sandbox Code Playgroud)

  • `nest_asyncio` 的文档提到了 [asyncio 的问题报告](https://bugs.python.org/issue22239),其中明确指出这不是 asyncio 的预期行为。所以我认为 `nest_asyncio` 是一个很大的黑客,我不相信我的代码库不会用新的 Python 版本破坏一切。 (2认同)
  • 这使我能够解决导入代码的问题(您无法自己更改它) (2认同)

And*_*ler 7

要添加到现有答案,请添加一个较短的版本:

  • 没有外部库
  • 允许在 Jupyter Notebook 内部或外部运行
  • 允许获取返回值
try:
    asyncio.get_running_loop() # Triggers RuntimeError if no running event loop
    # Create a separate thread so we can block before returning
    with ThreadPoolExecutor(1) as pool:
        result = pool.submit(lambda: asyncio.run(myfunc())).result()
except RuntimeError:
    result = asyncio.run(myfunc())
Run Code Online (Sandbox Code Playgroud)

注意: ifmyfunc的内部抛出 a并且在,RuntimeError中运行,将在子句中重新运行。因此,如果您可以抛出 a ,请考虑检查子句中的原因ThreadPoolExecutormyfuncexceptmyfuncRuntimeErrorRuntimeErrorexcept


ost*_*ach 5

我发现该unsync包对于编写在 Python 脚本和 Jupyter REPL 中行为相同的代码很有用。

import asyncio
from unsync import unsync


@unsync
async def demo_async_fn():
    await asyncio.sleep(0.1)
    return "done!"

print(demo_async_fn().result())
Run Code Online (Sandbox Code Playgroud)