Issue
I know that task.cancel()
doesn't do anything except setting a flag which says the task should be cancelled(here), so that in the next iteration of the event loop, a CancelledError
gets thrown into the wrapped coroutine.
Also I know that throwing exceptions into coroutines only happens at await
statement calls. The coroutine has to be in that state.
Now take a look at this:
import asyncio
async def coro_1():
print("inside coro_1")
try:
await asyncio.sleep(0)
except asyncio.CancelledError:
print("cancellation error occurred")
raise
async def main():
print("inside main")
new_task = asyncio.create_task(coro_1())
print("finishing main")
asyncio.run(main())
output:
inside main
finishing main
inside coro_1
cancellation error occurred
In main()
coroutine, I created a new task, but I didn't give it opportunity to run. (I didn't await on anything). The main()
coroutine finishes but the coro_1
executes!
Ok I know that this happened because I used asyncio.run()
but after I went through the source code, I couldn't understand why and where. I must be definitely wrong in one of the following steps.
when
coro_1()
task is created inmain
, it schedules a new callback(self.__step
) in the event loop for the next cycle. (inself._ready
queue).the
main
is finished, execution goes into the__exit__
method of theRunner
object(here).it then
.cancel()
s all remaining tasks here. Now we have ourcoro_1
task. It's_must_cancel
flag is set and its__step
is waiting to be executed in the next iteration with that exception set.Since the
exc
is set, we reach at this line:... else: result = coro.throw(exc) ...
Now how is that possible? our coro_1
is not in the state that we can throw exception to it. Someone must have executed it until the line await asyncio.sleep(0)
in the past. who and where?
I use Python 3.11.
Thanks in advance.
Solution
I found the answer.
First off, there is a queue called self._ready()
in the event loop within which all callbacks are ready to be executed.
These are the steps:
When we run our
main
coroutine using.run()
, it creates a new task, add its callback to theself._ready
, then it callsrun_until_complete()
. Ourself._ready
only contains that callback.In
run_until_complete
, it adds_run_until_complete_cb
callback to theadd_done_callback
of the future.(it's gonna be called the future is done). what is its job? it stops the event loop. it basically setsself._stopping
attribute of the event loop toTrue
, which is used here.Now it calls
run_forever()
which indeed iterates throughself._ready
queue and runs all callbacks. (right now we have only one. themain
coroutine).Note that callbacks are popped out of the
self._ready
in order to be called, so theself._ready
is empty when execution reaches the linenew_task = asyncio.create_task(coro_1())
. It then adds new callback to theself._ready
which is responsible for runningcoro_1
coroutine.the
main()
reaches the end and it's finished. what happens next? it's time for scheduling its_run_until_complete_cb
which we registered earlier.Now what is inside our
self._ready
? two callbacks, one for executingcoro_1
and one for executing_run_until_complete_cb
. The loop is not stopped yet. So another cycle of the loop is fired. It executescoro_1
until theawait
statement(it's now ready forCancelledError
exception), and also executes_run_until_complete_cb
. This latter callback, stops the eventloop. what is inside ourself._ready
? only one callback which is responsible for continuing thecoro_1
. (it scheduled itself back to the eventloop).Now it's time for
__exit__
method ofRunner
object to be called. what does it do? it cancels all remaining tasks. our only task iscoro_1
which is pending. Now in the third cycle of the event loop it throwsCancelledError
intocoro_1
.
Answered By - S.B
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.