python3 异步错误 asyncio.Semaphore RuntimeError: Task got Future attached to a different loop

本文探讨了在Python 3.7中使用asyncio模块时遇到的RuntimeError问题,详细解释了错误原因,并提供了解决方案,即在正确的事件循环中创建Semaphore。
部署运行你感兴趣的模型镜像

错误现象

asyncio.Semaphore RuntimeError: Task got Future attached to a different loop

asyncio.Semaphore RuntimeError:任务将Future连接到另一个循环

当我在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())

它因RuntimeError失败:

$ 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

错误原因

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

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“默认”循环,因此不能与通过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 loop 时,在创建 asyncio.Semaphore 时需要指定使用哪个 loop,例如 asyncio.Semaphore(2, loop=xxx_loop)

 

 

您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>首先,用户的问题是:在Python 3.6中使用asyncio.Queue时出现"Future <Future pending> attached to a different loop"错误的解决方案。 用户的查询引用了几个来源: - 引用[1]:提到了一个错误消息,关键词是"got Future attached to a different loop"。 - 引用[2]:讨论了asyncio的使用,包括wait、gather、future等概念。 - 引用[3]:具体提到了在asyncio.Semaphore中出现类似错误,并解释了asyncio.run()会创建一个新的事件循环。 根据系统指令: - 所有行内数学表达式必须用$...$格式。这个问题是关于编程的,不太可能有数学表达式,但如果有,比如涉及算法,我会处理。 - 独立公式用$$...$$,但这里可能不需要。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 引用的段落末尾添加引用标识,如[^1]。 用户指定的引用不是我必须直接引用的内容;它们是参考,我应该基于这些来回答问题。特别是引用[3]直接相关,因为它解释了错误的原因:使用asyncio.run()时,它会启动一个新的事件循环,如果Future对象是从不同的循环创建的,就会出错。 关键点回顾: - 错误:"Future attached to a different loop" 通常在尝试将一个Future对象关联到另一个事件循环时发生。 - 在Python asyncio中,每个事件循环都有自己的上下文。Future对象必须与创建它们的循环关联。 - 在Python 3.6中,asyncio API可能与新版本有所不同。Python 3.7引入了asyncio.run(),但用户在Python 3.6中,所以可能没有这个函数。 用户说"在Python 3.6中使用",所以我需要针对Python 3.6提供解决方案。 常见原因和解决方案: 1. **创建队列时的事件循环不一致**:如果在不同的循环中创建asyncio.Queue对象,可能会出错。队列应该在同一循环中使用。 2. **使用asyncio.run()或类似函数**:在Python 3.6中,asyncio.run()可能不可用(它在3.7引入)。用户可能在使用loop.run_until_complete()或其他方式。错误发生时,可能是因为代码尝试在多个循环中使用对象。 3. **引用[3]的洞察**:它提到使用asyncio.run()时,它会创建一个新循环。如果代码中使用了从旧循环创建的Future(比如semaphore或queue),就会出错。 - 解决方案:确保所有异步对象都在同一个事件循环中创建和使用。 针对用户的具体查询:使用asyncio.Queue时出现错误。 可能的原因: - 在创建Queue时和在使用Queue的协程中,事件循环不同。 - 可能在多个地方创建循环,导致不一致。 解决方案: - **确保在同一个事件循环中创建和使用Queue**。 - 在Python 3.6中,推荐的方法是显式地获取或创建事件循环。 - 避免在全局范围内创建异步对象,除非确保循环一致。 - 使用asyncio.get_event_loop()来获取当前循环。 标准做法: 1. 在主函数中,创建一个事件循环。 2. 在这个循环的上下文中创建所有异步对象。 3. 使用loop.run_until_complete()来运行协程。 如果错误是由于asyncio.run()之类的新函数引起的,但Python 3.6没有asyncio.run(),用户可能在模仿它。 引用[3]建议:asyncio.run()启动一个新循环,所以如果对象附加到旧循环,就会出错。解决方案是确保在run()内部创建对象。 在Python 3.6中,替代asyncio.run()的方式是: ```python loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: loop.close() ``` 所以,针对asyncio.Queue的解决方案: - 不要在全局范围内创建Queue。在协程内部或main函数中创建。 - 确保Queue在同一循环中被访问。 示例代码修复: ```python import asyncio async def producer(queue): for i in range(5): await queue.put(i) await asyncio.sleep(1) async def consumer(queue): while True: item = await queue.get() if item is None: break print(item) queue.task_done() async def main(): queue = asyncio.Queue() # 在main协程中创建队列 tasks = [ asyncio.ensure_future(producer(queue)), asyncio.ensure_future(consumer(queue)) ] await asyncio.gather(*tasks) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close() ``` 在这个例子中,队列在main协程中创建,确保了同一循环。 如果Queue是在其他地方创建的,可能会导致错误。 用户可能在代码中有类似问题。 生成相关问题: - 系统指令要求"生成相关问题"。在回答末尾添加相关问题。 - 相关问题应基于主题:asyncio、事件循环、Future错误等。 回答结构: - 使用中文回答。 - 结构清晰:解释错误原因,提供解决方案。 - 引用来源:在适当的地方添加引用标识。用户提供了引用[1],[2],[3],我应该基于这些引用内容添加标识。 - 例如,提到错误原因时引用[3],因为引用[3]解释了asyncio.run()创建新循环的问题。 - 由于引用是用户提供的上下文,不是外部来源,我应该视情况添加标识。 参考用户上一次的问题:用户说"参考站内引用",并给出了引用内容,所以我需要在回答中自然地引用它们。 可能的引用点: - 错误消息:"got Future attached to a different loop" 引用[1]。 - asyncio.run()创建新循环的解释 引用[3]。 - 整体asyncio概念 引用[2]。 回答大纲: 1. **错误解释**:解释"Future attached to a different loop"错误的原因。引用[3]提到这是由于事件循环不一致导致的。 2. **原因分析**:在Python 3.6中使用asyncio.Queue时,常见原因包括在全局作用域创建队列或在不同的循环中访问。 3. **解决方案**: - 建议在异步函数内部创建和使用队列。 - 使用asyncio.get_event_loop()确保同一循环。 - 避免在多个地方创建循环。 4. **代码示例**:提供一个简单的修复示例。 5. **相关问题**:生成2-3个相关问题。 相关问题 ideas: - Python asyncio中如何正确管理事件循环? - asyncio.Queue的其他常见错误有哪些? - 在Python 3.7及以上版本中,如何避免此类错误? 确保回答真实可靠:基于Python文档和常见实践。 最终回答用中文。 </think>### Python 3.6 asyncio.Queue出现"Future <Future pending> attached to a different loop"错误的解决方案 在Python 3.6中使用`asyncio.Queue`时,"Future attached to a different loop"错误通常是由于事件循环(event loop)不一致导致的。每个`Future`对象(包括`asyncio.Queue`的内部实现)必须与创建它的事件循环关联。如果代码在不同循环中创建和访问队列,就会引发此错误。常见原因包括:在全局作用域创建队列、通过`asyncio.run()`或其他方式隐式创建新循环时未重置对象引用,或在多个线程中错误共享循环。下面我将一步步解释原因并提供解决方案,基于用户提供的引用内容。 #### 错误原因分析 - **事件循环不一致**:在Python asyncio中,每个事件循环是独立的。`asyncio.Queue`在内部使用`Future`对象,如果队列在一个循环中创建,但在另一个循环中被访问(例如在协程或任务中),就会触发错误。引用[3]明确指出,当使用`asyncio.run()`(或类似函数)启动新循环时,如果对象附加到旧循环,就会抛出`RuntimeError: Task got Future attached to a different loop`[^3]。 - **Python 3.6的特定性**:Python 3.6没有内置`asyncio.run()`(它在Python 3.7引入),但用户可能使用`loop.run_until_complete()`或自定义事件循环管理。错误常见于: - 在全局作用域初始化队列(例如`queue = asyncio.Queue()`),导致队列绑定到初始循环。 - 在主函数中创建新循环(如`asyncio.new_event_loop()`),但队列已绑定到旧循环。 - 异步任务(task)在错误循环中调用队列方法(如`queue.put()`或`queue.get()`)。 - **引用[2]的上下文**:`asyncio`使用`Future`和`Task`管理异步操作,`Queue`依赖于这些机制。错误可能源于`wait`、`gather`或任务嵌套时循环切换不协调[^2]。 #### 解决方案 要解决此错误,核心原则是**确保`asyncio.Queue`的创建和使用在同一事件循环中完成**。以下是针对Python 3.6的推荐方法: 1. **避免在全局作用域创建队列**: - 将队列的初始化移至异步函数(coroutine)内部,确保它绑定到当前事件循环。 - 错误示例:全局定义`queue = asyncio.Queue()`,然后在`main()`中使用。 - 正确做法:在`async def main():`内创建队列。 2. **显式管理事件循环**: - Python 3.6中,使用`asyncio.get_event_loop()`获取当前循环,并在同一个循环中运行所有任务。 - 不要使用`asyncio.new_event_loop()`手动创建新循环,除非必要,这会增加不一致风险。 - 引用[3]建议:如果使用类似`asyncio.run()`的自定义逻辑,确保队列在“run”函数内部创建,避免附加到外部循环[^3]。 3. **代码重构示例**: 以下是一个修复后的完整示例,展示如何在Python 3.6中正确使用`asyncio.Queue`: ```python import asyncio async def producer(queue): for i in range(5): await queue.put(i) # 在同一循环中放入队列 print(f"Produced {i}") await asyncio.sleep(1) await queue.put(None) # 信号结束 async def consumer(queue): while True: item = await queue.get() # 在同一循环中获取队列 if item is None: break print(f"Consumed {item}") queue.task_done() async def main(): queue = asyncio.Queue() # 在main协程中创建队列,确保绑定到当前循环 # 创建任务 producer_task = asyncio.ensure_future(producer(queue)) consumer_task = asyncio.ensure_future(consumer(queue)) # 等待所有任务完成 await asyncio.gather(producer_task, consumer_task) if __name__ == "__main__": # Python 3.6兼容的事件循环管理 loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: loop.close() ``` **关键修复点**: - 队列在`main()`协程内创建,使其自动绑定到`run_until_complete()`使用的循环。 - 使用`asyncio.ensure_future()`创建任务,确保它们在同一个循环中运行。 - 避免全局变量,所有异步操作包含在事件循环上下文中。 4. **其他注意事项**: - **线程安全**:如果涉及多线程,使用`loop.call_soon_threadsafe()`(引用[2]提到的`call_soon_threadsafe`)来跨线程调度队列操作,但优先保证单循环模型[^2]。 - **错误处理**:添加异常捕获,例如在`queue.get()`时处理`asyncio.CancelledError`。 - **Python 3.7+优化**:如果升级到Python 3.7及以上,`asyncio.run()`简化了循环管理,但仍需遵守“队列在协程内部创建”的原则。 #### 总结 此错误源于事件循环上下文不匹配,解决方案是统一队列的创建和使用循环。在Python 3.6中,通过显式循环管理和避免全局初始化,可以有效避免问题。如果代码结构复杂,建议使用日志打印当前循环ID(`id(asyncio.get_event_loop())`)来调试不一致点。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值