关于asyncio中_run_until_complete_cb触发时机与VSCode调试的问询
Let's break down your questions step by step, since asyncio's internal flow can feel opaque at first glance.
1. When does the Future complete, triggering _run_until_complete_cb?
First, let's recap the core flow of your code:
asyncio.run(main())creates an event loop under the hood, then callsloop.run_until_complete(main())to execute your coroutine.run_until_completewraps your main coroutine into aTask(a subclass ofFuture), then registers_run_until_complete_cbas a done callback viaadd_done_callback.
The critical moment when the Future (your main Task) transitions from _PENDING to completed happens when your main coroutine finishes executing:
- After your main coroutine runs
print("Wake up!"), it reaches the end of the function (an implicitreturn None). - The
Taskinstance wrapping your coroutine handles this completion in its internalstepmethod. When the coroutine iterator is exhausted (no more yields), the Task callsself.set_result(None)to mark itself as finished. set_resultchanges the Task's_statefrom_PENDINGto_FINISHED, then iterates over all callbacks stored in_callbacks(including_run_until_complete_cb). It schedules each callback withloop.call_soonto run in the next iteration of the event loop.
Once _run_until_complete_cb executes, it sets the event loop's _stopping flag to True, which tells the loop to exit after processing any remaining pending callbacks.
2. Debugging Future/Task methods in VS Code
VS Code's debugger skips over standard library code (like asyncio's Future/Task methods) by default, which is why you're not seeing those internal calls. Here are practical fixes to trace into these methods:
Disable "Just My Code":
Open your.vscode/launch.jsonconfiguration, and add or set"justMyCode": false. This tells the debugger to step into non-user code (like asyncio's source) instead of skipping it. A sample launch config might look like this:{ "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "justMyCode": false } ] }Enable asyncio debug mode:
Change your call toasyncio.run(main(), debug=True). This enables asyncio's built-in debugging infrastructure, which adds extra checks, makes internal state transitions more visible, and helps the debugger track async flows more reliably.Set targeted breakpoints:
If you want to break exactly when the Task completes, you can set a breakpoint directly in asyncio'sset_resultmethod (look for theFutureclass in your Python installation'sasyncio/futures.pyfile), or in thestepmethod of theTaskclass (asyncio/tasks.py). This lets you observe exactly when the state changes and callbacks are triggered.Inspect Task state in the debug console:
While debugging, you can use the debug console to check the state of your Task. For example, temporarily modify your code to capture the Task instance:if __name__ == "__main__": loop = asyncio.get_event_loop() task = loop.create_task(main()) loop.run_until_complete(task)Then, in the debug console, run
task._stateortask.done()to check if it's completed at any point in the flow.
Hope this clears up the confusion around asyncio's cleanup flow and helps you debug more effectively!
内容的提问来源于stack exchange,提问作者BrainVader

