V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
RandomAccess
V2EX  ›  Python

关于 Python event loop

  •  1
     
  •   RandomAccess · 2021-02-03 16:46:23 +08:00 · 2497 次点击
    这是一个创建于 1149 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有这样一个协程

    async def function(**kwargs):
        session = aiohttp.ClientSession()
        response = await session.request(**kwargs)
        pass
    

    如果使用 asyncio.run(function(arg=bala))和 new_event_loop 会出现

    RuntimeError: Timeout context manager should be used inside a task
    

    使用

    loop = asyncio.get_event_loop()
    loop.run_until_complete(function(arg=bala))
    loop.close()
    

    不会出现上下文管理的问题 这两者的区别在哪里,翻了半天官方文档和 csdn 的转载文实在无法理解 TAT

    14 条回复    2021-02-04 21:50:22 +08:00
    Lycnir
        1
    Lycnir  
       2021-02-03 16:59:20 +08:00
    python3.7 正常
    cissoid
        2
    cissoid  
       2021-02-03 17:00:23 +08:00   ❤️ 1
    翻翻官方文档,

    asyncio.run:
    This function cannot be called when another asyncio event loop is running in the same thread.
    linw1995
        3
    linw1995  
       2021-02-03 17:23:30 +08:00   ❤️ 1
    应该是因为你把同个 ClientSession 用在两个不同的 eventloop 上。asyncio.run 跑完后,event_loop 会关闭掉。在新的 event_loop 跑的时候,因为用的同一个 ClientSession,它用了上一个关闭了的 event_loop,导致这个异常抛出。

    你这个是旧版的 aiohttp 吧,新版的 async_timeout 不会出现这个问题。
    RandomAccess
        4
    RandomAccess  
    OP
       2021-02-03 17:59:06 +08:00
    DeprecationWarning: The object should be created within an async function


    看样子应该是 session 在类实例化时在普通函数中创建的产生的问题,一个包含异步函数的对象在只能在 async 函数中创建,包括自己封装的类
    ClericPy
        5
    ClericPy  
       2021-02-03 21:06:15 +08:00   ❤️ 1
    class TimerContext(BaseTimerContext):
    """ Low resolution timeout context manager """

    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
    raise RuntimeError(
    "Timeout context manager should be used " "inside a task"
    )


    老问题了, 所以我现在用到 aiohttp 尽量不用 asyncio.run, 因为它一定新建一个 loop, 然而 Session 的默认 Timeout 对象却是在协程外面初始化的, 导致两个循环不一致, 见下面源码

    https://github.com/aio-libs/aiohttp/blob/742a8b6d09b2623670ddede838c913d2a8a4d89e/aiohttp/client.py#L161

    据说 aiohttp 4.0 以后会好, 但是 4.0 一直发不出来

    想解决的话, 简单的就是暂时别用 asyncio.run. 或者在协程函数里 import 它, 或者手动指定 Timeout 试试
    RandomAccess
        6
    RandomAccess  
    OP
       2021-02-04 08:22:52 +08:00 via iPhone
    @ClericPy 谢谢大佬
    如果业务场景需要 session 在两个阻塞的事件循环中传递是不是不可行的
    guochao
        7
    guochao  
       2021-02-04 10:48:44 +08:00   ❤️ 1
    简单说 python 的 event loop 是一个单进程单线程单个 loop 执行 coroutine 的过程。asyncio.run 就是一个创建 loop 并且 run until complete 的过程,new event loop 又建了一个,没有必要。

    解决办法就是让整个程序的入口是一个 async 函数,在 async 函数中配置程序,新建各种实例,启动应用。在__main__里面 asyncio.run(entrypoint())就行。然后在任何其他地方都不要 new_event_loop 或者 asyncio.run

    如果要执行一个 coroutine 但是不等待结束,可以 asyncio.ensure_future 或者 3.7 以上的新 API asyncio.create_task,这两个函数都是在 get_event_loop()返回的 loop 上执行对应的函数,会返回一个 Task
    如果要在一个 coroutine 中执行一个 coroutine 并且等待结束,那就直接 await 。
    如果要执行一个同步过程,可以用 run_in_executor,返回一个 Future
    RandomAccess
        8
    RandomAccess  
    OP
       2021-02-04 12:18:35 +08:00
    @guochao thanks
    ClericPy
        9
    ClericPy  
       2021-02-04 20:30:21 +08:00   ❤️ 1
    @RandomAccess
    时间循环尽可能只用一个, 就算协程用的很熟悉的人, 也很少去多个线程上跑多个 loop
    所以 Python 3.9 还是 3.10 之后, 很多内置的协程方法都去掉了 loop 参数, 默认都从 running loop 里面获取

    你要做的是, 首先保证整个程序只留有一个事件循环(因为多个也没有意义, await 不是阻塞, 是等待), 然后在里面传递 session 对象就是合法的了

    然后你说的阻塞, 协程实际上是非阻塞的设计, 你的阻塞可能是 await 关键词, 那个是等待不算阻塞. 所以你如果想并发开始多个任务, 可以把 "协程函数" (async def 声明的)执行获得的 "协程对象" 创建为 Task, 它就会开始执行但是又不会阻塞, 创建 Task 的方式有 asyncio.ensure_future(some_coro) 或者 asyncio.create_task(coro), 后者是 3.7 以后新增的
    RandomAccess
        10
    RandomAccess  
    OP
       2021-02-04 21:14:56 +08:00 via iPhone
    @ClericPy 确实是应该一个进程或线程只使用一个事件循环,只是业务场景需要进行一次长 io 且逻辑复杂的操作 A 获取操作 B 所需要的必要参数,然后任务 B 去执行多并发的也是长 io 的操作,AB 有着前后顺序,逻辑上应当是阻塞的,但是各自内部操作是无序的长 io,想尝试多个事件循环是否适应这个场景或多个不同并发的协程有没有好的顺序执行方案
    RandomAccess
        11
    RandomAccess  
    OP
       2021-02-04 21:16:57 +08:00 via iPhone
    @RandomAccess 操作 A 和 B 各自都是嵌套的协程
    ClericPy
        12
    ClericPy  
       2021-02-04 21:30:20 +08:00   ❤️ 1
    @RandomAccess
    一样是一个事件循环来解决的, 先后顺序的阻塞用 await 或者协程队列都行
    非要多线程或者非协程的, 走 run_in_executor
    RandomAccess
        13
    RandomAccess  
    OP
       2021-02-04 21:42:49 +08:00 via iPhone
    @ClericPy thanks
    RandomAccess
        14
    RandomAccess  
    OP
       2021-02-04 21:50:22 +08:00 via iPhone
    @ClericPy 对协程的理解不是很深刻,刚才想通 await 的等待交出执行权其实也可以在代码逻辑实现前后顺序,没必要复杂化多个事件循环
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5415 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 07:51 · PVG 15:51 · LAX 00:51 · JFK 03:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.