I'm watching import asyncio: Learn Python's AsyncIO #3 - Using Coroutines. The instructor gave the following example:
import asyncio
import datetime
async def keep_printing(name):
while True:
print(name, end=" ")
print(datetime.datetime.now())
await asyncio.sleep(0.5)
async def main():
group_task = asyncio.gather(
keep_printing("First"),
keep_printing("Second"),
keep_printing("Third")
)
try:
await asyncio.wait_for(group_task, 3)
except asyncio.TimeoutError:
print("Time's up!")
if __name__ == "__main__":
asyncio.run(main())
The output had an exception:
First 2020-08-11 14:53:12.079830
Second 2020-08-11 14:53:12.079830
Third 2020-08-11 14:53:12.080828
First 2020-08-11 14:53:12.580865
Second 2020-08-11 14:53:12.580865
Third 2020-08-11 14:53:12.581901
First 2020-08-11 14:53:13.081979
Second 2020-08-11 14:53:13.082408
Third 2020-08-11 14:53:13.082408
First 2020-08-11 14:53:13.583497
Second 2020-08-11 14:53:13.583935
Third 2020-08-11 14:53:13.584946
First 2020-08-11 14:53:14.079666
Second 2020-08-11 14:53:14.081169
Third 2020-08-11 14:53:14.115689
First 2020-08-11 14:53:14.570694
Second 2020-08-11 14:53:14.571668
Third 2020-08-11 14:53:14.635769
First 2020-08-11 14:53:15.074124
Second 2020-08-11 14:53:15.074900
Time's up!
_GatheringFuture exception was never retrieved
future: <_GatheringFuture finished exception=CancelledError()>
concurrent.futures._base.CancelledError
The instructor tried to the handle the CancelledError
by adding a try/except
in keep_printing
:
async def keep_printing(name):
while True:
print(name, end=" ")
print(datetime.datetime.now())
try:
await asyncio.sleep(0.5)
except asyncio.CancelledError:
print(name, "was cancelled!")
break
However, the same exception still occurred:
# keep printing datetimes
...
First was cancelled!
Second was cancelled!
Third was cancelled!
Time's up!
_GatheringFuture exception was never retrieved
future: <_GatheringFuture finished exception=CancelledError()>
concurrent.futures._base.CancelledError
The instructor then just proceeded to other topics and never came back to this example to show how to fix it. Fortunately, through experimentation, I discovered that we could fix it by adding another try/except
under the except asyncio.TimeoutError:
in the main
async function:
async def main():
group_task = asyncio.gather(
keep_printing("First"),
keep_printing("Second"),
keep_printing("Third")
)
try:
await asyncio.wait_for(group_task, 3)
except asyncio.TimeoutError:
print("Time's up!")
try:
await group_task
except asyncio.CancelledError:
print("Main was cancelled!")
The final output was:
# keep printing datetimes
...
First was cancelled!
Second was cancelled!
Third was cancelled!
Time's up!
Main was cancelled!
In fact, with this edition of main
, we don't even need the try...except asyncio.CancelledError
in keep_printing
. It would still work fine.
Why was that? Why did catching CancelledError
in main
work but not in keep_printing
? The way that the video instructor dealt with this exception only made me more confused. He didn't need to change any code of keep_printing
in the first place!