asyncio.Semaphore RuntimeError: Task got Future 附加到不同的循环

Mes*_*ssa 15 python semaphore python-3.x python-asyncio

当我在 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())
Run Code Online (Sandbox Code Playgroud)

它因运行时错误而失败:

$ 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
Run Code Online (Sandbox Code Playgroud)

Mes*_*ssa 28

这是因为 Semaphore 构造函数_loopasyncio/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()
Run Code Online (Sandbox Code Playgroud)

但是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()
    ...
Run Code Online (Sandbox Code Playgroud)

Semaphore在外部启动asyncio.run()抓取 asyncio“默认”循环,因此不能与使用asyncio.run().

解决方案

Semaphore从 调用的代码启动asyncio.run()。您必须将它们传递到正确的地方,有更多的可能性如何做到这一点,例如您可以使用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())
Run Code Online (Sandbox Code Playgroud)

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

  • 为了便于搜索,您介意添加“asyncio.Lock、.Event、.Condition”等词吗?它们似乎具有相同的机制。- 我对 Lock 进行了测试,它绝对适用。 (2认同)