【问题标题】:asyncio.Semaphore RuntimeError: Task got Future attached to a different loopasyncio.Semaphore RuntimeError: Task got Future 附加到不同的循环
【发布时间】:2019-04-30 09:38:15
【问题描述】:

当我在 Python 3.7 中运行此代码时:

import asyncio

sem = asyncio.Semaphore(2)

async def work():
    async with sem:
        print('working')
        await asyncio.sleep(1)

async def main():
    await asyncio.gather(work(), work(), work())

asyncio.run(main())

运行时错误失败:

$ python3 demo.py
working
working
Traceback (most recent call last):
  File "demo.py", line 13, in <module>
    asyncio.run(main())
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "demo.py", line 11, in main
    await asyncio.gather(work(), work(), work())
  File "demo.py", line 6, in work
    async with sem:
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/locks.py", line 92, in __aenter__
    await self.acquire()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/locks.py", line 474, in acquire
    await fut
RuntimeError: Task <Task pending coro=<work() running at demo.py:6> cb=[gather.<locals>._done_callback() at /opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py:664]> got Future <Future pending> attached to a different loop

【问题讨论】:

    标签: python python-3.x semaphore python-asyncio


    【解决方案1】:

    这是因为 Semaphore 构造函数设置了它的_loop 属性——在asyncio/locks.py

    class Semaphore(_ContextManagerMixin):
    
        def __init__(self, value=1, *, loop=None):
            if value < 0:
                raise ValueError("Semaphore initial value must be >= 0")
            self._value = value
            self._waiters = collections.deque()
            if loop is not None:
                self._loop = loop
            else:
                self._loop = events.get_event_loop()
    

    但是asyncio.run() 开始了一个全新的循环——在asyncio/runners.py 中,它也在文档中提到:

    def run(main, *, debug=False):
        if events._get_running_loop() is not None:
            raise RuntimeError(
                "asyncio.run() cannot be called from a running event loop")
    
        if not coroutines.iscoroutine(main):
            raise ValueError("a coroutine was expected, got {!r}".format(main))
    
        loop = events.new_event_loop()
        ...
    

    asyncio.run() 之外发起的Semaphore 获取异步“默认”循环,因此不能与asyncio.run() 创建的事件循环一起使用。

    解决方案

    asyncio.run() 调用的代码启动Semaphore。您必须将它们传递到正确的位置,有更多的可能性如何做到这一点,例如您可以使用contextvars,但我只举一个最简单的例子:

    import asyncio
    
    async def work(sem):
        async with sem:
            print('working')
            await asyncio.sleep(1)
    
    async def main():
        sem = asyncio.Semaphore(2)
        await asyncio.gather(work(sem), work(sem), work(sem))
    
    asyncio.run(main())
    

    asyncio.Lockasyncio.Eventasyncio.Condition 可能也存在同样的问题(和解决方案)。

    【讨论】:

    • 哦。我遇到了同样的问题。感谢您的回答:)
    • 您介意为asyncio.Lock, .Event, .Condition 添加字词,以便搜索吗?它们似乎具有相同的机制。 - 我对它进行了 Lock 测试,它确实适用。
    • 这是很好的解释。非常感谢这个简单的例子! :)
    猜你喜欢
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    • 2020-05-20
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多