如何在调试异步函数时检索原始调用堆栈?
问题描述:
鉴于此示例代码如何在调试异步函数时检索原始调用堆栈?
#!/usr/bin/python3
import asyncio
async def f1():
await f2()
async def f2():
try:
await asyncio.sleep(1)
except BaseException as exc:
import pdb;pdb.set_trace()
pass
async def main():
f = asyncio.ensure_future(f1())
await asyncio.sleep(0.5)
f.cancel()
await f
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在断点处的堆栈跟踪看起来是这样的:
(Pdb) w
/tmp/t.py(19)<module>()
-> loop.run_until_complete(main())
/usr/lib/python3.5/asyncio/base_events.py(325)run_until_complete()
-> self.run_forever()
/usr/lib/python3.5/asyncio/base_events.py(295)run_forever()
-> self._run_once()
/usr/lib/python3.5/asyncio/base_events.py(1254)_run_once()
-> handle._run()
/usr/lib/python3.5/asyncio/events.py(125)_run()
-> self._callback(*self._args)
/usr/lib/python3.5/asyncio/tasks.py(293)_wakeup()
-> self._step(exc)
/usr/lib/python3.5/asyncio/tasks.py(241)_step()
-> result = coro.throw(exc)
> /tmp/t.py(11)f2()
-> pass
这是不好的,因为我再也看不到F2()最初是从F1被称为() 。
如何找回原来的调用堆栈,因为Python是能够做到这一点通过简单的单步?:
$ python3 t.py
> /tmp/t.py(11)f2()
-> pass
(Pdb) s
--Return--
> /tmp/t.py(11)f2()->None
-> pass
(Pdb) s
--Call--
> /tmp/t.py(5)f1()
-> await f2()
(Pdb) s
--Return--
> /tmp/t.py(4)f1()->None
答
没办法,对不起。
这是不可能的,至少在Python 3.5
UPD
内蟒框架具有至外框(fr.f_back
)的引用,它使能够显示堆栈跟踪。
但它没有提及外部协程等待内部的一个。
您的代码演示了一个非常有趣的案例。
对于简单的例子:
#!/usr/bin/python3
import asyncio
import sys
import traceback
async def f1():
await f2()
async def f2():
import pdb;pdb.set_trace()
loop = asyncio.get_event_loop()
loop.run_until_complete(f1())
,我们将看到在跟踪你的协同程序:
(Pdb) w
/tmp/3.py(12)<module>()
-> loop.run_until_complete(f1())
/usr/lib/python3.5/asyncio/base_events.py(325)run_until_complete()
-> self.run_forever()
/usr/lib/python3.5/asyncio/base_events.py(295)run_forever()
-> self._run_once()
/usr/lib/python3.5/asyncio/base_events.py(1254)_run_once()
-> handle._run()
/usr/lib/python3.5/asyncio/events.py(125)_run()
-> self._callback(*self._args)
/usr/lib/python3.5/asyncio/tasks.py(239)_step()
-> result = coro.send(None)
/tmp/3.py(7)f1()
-> await f2()
> /tmp/3.py(9)f2()->None
-> import pdb;pdb.set_trace()
但在你的情况的情况是不同的:f2()
等待sleep()
。
它的内部工作以下列方式:sleep()
返回未来对象,f2()
收益率(不返回)回f1()
再次产生的未来。在这种情况下最上面的呼叫者任务f
创建main
通过ensure_future()
呼叫。
要取消任务取消它最内侧服务员(未来通过sleep()
返回),而无需通过f1 -> f2 -> sleep
链自上而下的打算,但明确取消sleep
及以上的自下而上的顺序链弹出。
这就是为什么你只能看到栈上的最后一个协程 - 其他所有的协程都会在异常展开时出现。
那么,单步跳过f2()的结尾将返回到f1,所以信息必须隐藏起来*某处* ... –
我已经更新了我的答案。我希望我的解释能够阐明你的问题。 –
嗯。所以显然这一切都是透明地由C运行时处理的,而我从来没有注意过任何它?因为当我单步通过我的f2()的末尾时,我立即在f1()中结束。 (我相应地更新了这个问题。) –