max*_*max 4 weak-references python-internals python-asyncio
状态的 Python 文档asyncio.create_task:
\n\n重要提示:保存对此函数结果的引用,以避免任务在执行过程中消失。事件循环仅保留对任务的弱引用。未在其他地方引用的任务可能会随时被垃圾回收,甚至在它完成之前也是如此。对于可靠的 \xe2\x80\x9cfire-and-forget\xe2\x80\x9d 后台任务,请将它们收集在一个集合中:
\n
asyncio在创建的任务完成之前使用弱引用而不是强引用有什么好处?正如上面的警告所表明的那样,保留强引用肯定至少有一个好处——这意味着弱引用可能会带来抵消性的好处。请注意,对于强引用,asyncio完成后可以完全删除引用或切换到弱引用,具体取决于异步逻辑的要求。
文档警告中隐含的用例是:我们不想等待任务(特别是不使用任务的返回值),但我们确实希望给任务一个最终运行的机会。
\n请注意,asyncio.TaskGroup上下文确实保留了对其创建的任务的强引用。但是,它不适合上述用例,因为退出时,上下文会阻塞(它等待所有任务完成)。(此外,即使任务完成后,它也会保留对任务的强引用,因此如果上下文的生存时间比任务长得多,则会导致内存泄漏。)
抱歉,如果不是“为什么”的真正答案 - 我认为无论谁真正实现了它,都可以提出真正的动机 - 如果除了您在问题中引用的内容之外还有其他动机。
但另一方面,在弱引用对象即将被切换并执行之前,我们甚至无法检查它是否具有任何硬引用:在任务创建时,对其的唯一硬引用将返回给调用者。因此,一旦由于担心资源泄漏而决定不保留对任务的硬引用,“炸弹”就消失了。这可能只是后来才被察觉,但他们将其作为硬引用可能会改变某些工作负载的太多工作行为。
更新作为持有硬引用的缺点:如果任务像“即发即忘”一样创建,最终完成的任务将携带其结果(或异常)数据,并且不能简单地“删除”:它们需要无限期地保留,这可能是服务器类型应用程序的主要资源泄漏。另一方面,是否要“划清界限”,将完整的任务视为“孤儿”并可以丢弃?因此,看来实现选择的“线”,即使不是那么好,也是在其他地方有硬引用的任务。
在源代码中,当创建一个任务时,它会立即注册为ready内部运行循环结构 - 这是一个硬引用 - 当循环在迭代中循环并且无法调用其中的任何任务时,该硬引用将被删除。这种“下降”不是确定性的,也许是一个可以修复的错误。
/更新
也许现在这个问题已经得到了一些认识,除了这个通知之外,(我上周也在 Twitter 上讨论了这个问题,知名 Python 人士担心这种行为),他们采取了不兼容的改变的步骤 -或者提出一个create_task做正确事情的继任者。正如您所说,任务组并不完全是这样 - 它们更多的是替代品asyncio.gather- 即使有一些缺点(如果__exit__单个任务抛出异常,整个组将被取消,没有实际的解决方法)
举个例子,我做了一些实验,结果非常糟糕,从大约 2500 个并发任务开始,使用以下代码进行随机任务丢弃:
import asyncio, random
async def blah(n):
await asyncio.sleep(random.random())
results.add(n)
async def main(m):
for i in range(m):
asyncio.create_task(blah(i))
await asyncio.sleep(1.01)
def doit(m):
global results
results = set()
asyncio.run(main(m))
try:
assert list(range(m)) == list(results)
except AssertionError:
print(f"For {m} tasks, missing {m - len(results)} results: {set(range(m)) - results}")
Run Code Online (Sandbox Code Playgroud)
在创建任务的循环中包含一个await asyncio.sleep(0) 循环,可以使其完美地运行多达数百万个任务。
| 归档时间: |
|
| 查看次数: |
506 次 |
| 最近记录: |