调试异步功能时,如何检索原始调用堆栈?

Mat*_*chs 5 python-3.x python-asyncio

给定此示例代码

#!/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())
Run Code Online (Sandbox Code Playgroud)

断点处的堆栈跟踪如下所示:

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

这很糟糕,因为我不再看到最初是从f1()调用f2()的。

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

And*_*lov 4

没办法,抱歉。

这是不可能的,至少在Python 3.5中是不可能的

UPD

Python 内部框架引用了外部框架( 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())
Run Code Online (Sandbox Code Playgroud)

我们将在跟踪中看到您的协程:

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

但在你的情况下,情况有所不同:f2()等待sleep()

它在内部按以下方式工作:sleep()返回一个future 对象f2()将其生成(不是返回),然后f1()再次生成 future。在这种情况下,最顶层的调用者是通过调用创建的任务fmainensure_future()

当您取消任务时,它会取消大多数内部等待者(由 返回的 future sleep()),而不经过f1 -> f2 -> sleep自上而下的链,而是显式取消sleep并按自下而上的顺序在链上弹出。

这就是为什么你只看到堆栈上的最后一个协程——所有其他协程都会在异常展开时出现在上面。