此示例代码无限期挂起:
import asyncio
async def main():
async def f():
await g_task
async def g():
await f_task
f_task = asyncio.create_task(f())
g_task = asyncio.create_task(g())
await f_task
asyncio.run(main())
我正在寻找一种自动检测和处理死锁的方法,就像GoLang一样。
到目前为止,我想出了asyncio.wait_for()
的一种变体:
[EDIT]大修设计
https://gist.github.com/gimperiale/549cbad04c24d870145d3f38fbb8e6f0
原始代码中的1行更改:
await wait_check_deadlock(f_task)
它可以工作,但是有两个主要问题:
asyncio.Task._fut_waiter
,它是CPython的实现细节aw.cancel()
似乎无能为力。如果我发现辅助函数引发了RecursionError,则asyncio.run()在尝试取消所有任务时会引发另一个RecursionError。有没有更强大的解决方案?
答案 0 :(得分:1)
对避免死锁的研究已经很多,存在一些可行的解决方案,但是在一般情况下,这个问题是不确定的(我认为可以将其简化为停止问题)。
为说明实用性,请考虑以下问题:
await asyncio.sleep(2 ** (1 / random.random()))
根据您的运气,它会很快返回,或者“实际上永远不会”。
此技巧可用于表明无法预测基于回调的程序:
f = asyncio.Future()
async foo():
await asyncio.sleep(2 ** (1 / random.random()))
f.set_result(None)
async bar():
await f
await asyncio.gather(foo(), bar())
同样,它可以应用于您的“纯”异步/等待程序:
async def f():
await g_task
async def g():
await asyncio.wait(f_task,
asyncio.sleep(2 ** (1 / random.random())),
return_when=asyncio.FIRST_COMPLETED)
f_task = asyncio.create_task(f())
g_task = asyncio.create_task(g())
await f_task
同时,不完美但实用的死锁检测器可能非常有用,请考虑将代码发布到核心asyncio开发人员和/或独立库中。
当前的做法是使用PYTHONASYNCIODEBUG=1
运行测试,该测试显示未等待的任务(在读取结果/异常之前已销毁)。
您的库可能会更好,例如,它可以报告某些任务花费的时间长于X,或者取决于给定任务的任务DAG何时增长。