I am still learning the asyncio module in python 3.10. Below is a code I wrote trying to understand how to run coroutines as tasks on an event loop. the output I am getting is different from what I expected. Please help me understand why the output is what it is.
import asyncio
async def f(x):
try:
print(f"Inside f({x})...")
await asyncio.sleep(x)
print(f'slept for {x} seconds!!')
return x
except asyncio.CancelledError: # Execute this part only when the task is cancelled.
print(f'Cancelled({x})')
async def main():
loop = asyncio.get_running_loop()
task1 = loop.create_task(f(1), name="task1")
task2 = loop.create_task(f(2), name="task2")
task3 = loop.create_task(f(3), name="task3")
print(task1.done())
print(task2.done())
print(task3.done())
asyncio.run(main())
I expect this code to exit main() immediately after print(task3.done()) (as I am not await ing any task) and only run the exception handling part of the coroutine function f(x), but the event loop runs the functionalities of f(1), f(2) and f(3) exactly one time before actually exiting (Hence those Inside f( ) messages in output). I tried using asyncio.create_task() too, but the output is same in both cases.
My doubts are
Why do all the tasks on the event loop run once before getting cancelled?
What is the execution order (if there is any, is it fixed or random?)
This documentation says that tasks ran in background while main process continues. They just don’t return anything till you await them. But, as you know, print() is not a returning method. So, your program prints something, and while waiting(via asyncio.sleep()), no code lefts that haven’t been executed in your main method. (I think that’s because the interpreter is really fast.)And because you haven’t used any await blocks, your tasks are cancelled.(But I have no idea about the cancelling order.) This is what I understood from the documentary. So, know that I am not professional at asyncio module and these are just my ideas.
You may want to try this code(I’ve removed the “sleepings”):
import asyncio
async def f(x):
try:
print(f"Inside f({x})...")
print(f'NOT slept!!')#edited line
return x
except asyncio.CancelledError: # Execute this part only when the task is cancelled.
print(f'Cancelled({x})')
async def main():
loop = asyncio.get_running_loop()
task1 = loop.create_task(f(1), name="task1")
task2 = loop.create_task(f(2), name="task2")
task3 = loop.create_task(f(3), name="task3")
print(task1.done())
print(task2.done())
print(task3.done())
asyncio.run(main())
The code above doesn’t print anything about cancelling. But it prints all other stuff.I think, this situation has a sensible reason:
Interpreter successfully and completely executes the function code (as a background-likely-process) -although it doesn’t return anything- before it finishes executing your main method.
SO, if it(interpreter) wouldn’t have been finished executing your function code when it finished executing your main method(and couldn’t find anything about await)(this happens when you put some sleepings or when the code is too long); a cancellation would have been occur.
async def f(x):
try:
print(f"Inside f({x})...")
print(f'NOT slept!!')#edited line
return x
except asyncio.CancelledError: # Execute this part only when the task is cancelled.
print(f'Cancelled({x})')
In this, there is no way of giving back the control to event loop because printf statements will just execute and task is done. Your effort is much appreciated but, my doubt is not why I am getting the cancelling messages.
To put my question simply: There are 3 tasks on event loop i.e task1, task2, task3 apart from the main() that is also on the event loop. My question is, will the tasks start executing first or the print statements in main() ?
By adding some statements that print time, I found that the print statements in main() run first. After that, there is nothing more to run so the main() should exit and all the tasks should be cancelled, so only cancelled messages should be seen… What actually happens is, there are messages from try block before the cancellation message. Why the try block is even getting executed when the tasks are supposed to cancel before they even started to run?
Okay, I have already understood your question.But I was a bit confusing, I think. Try code blocks start executing (they never pre-check the code inside them) the code inside. When the code crashes while executing, our except block comes here for help.This is the general behavior of try and except blocks.
And, your task starts running as a background process. While this process, if something makes it stop or slow(and if it haven’t been awaited in the “future”);and if it can’t be faster than your main method, it crashes. This makes your program to do what except says.
The documentation you shared on Coroutines and Tasks says:
To cancel a running Task use the cancel() method. Calling it will cause the Task to throw a CancelledError exception into the wrapped coroutine. If a coroutine is awaiting on a Future object during cancellation, the Future object will be cancelled.
That means, only the future objects that are being awaited will be cancelled? And any normal lines of code would still execute even in case of exception handling inside the coroutine ?
In the coroutine function below
async def f(x):
try:
print(f"Inside f({x})...")
await asyncio.sleep(x)
print(f'slept for {x} seconds!!')
return x
except asyncio.CancelledError: # Execute this part only when the task is cancelled.
print(f'Cancelled({x})')
asyncio.sleep(x) is a future object and it’s being awaited. So if what I understood from the documentation is correct, only this should be cancelled and print statements being non-future objects, they should execute normally and when code tries to execute the future object (await asyncio.sleep(x) in this case), it will raise a CancelledError and proceed to except block.
But, doesn’t the documentation explain manual cancelling here? That’s what I understand here. And I think,in the quote below, they wanted to say “If there is a future object depending on this task, it will also been cancelled.”