This is an awesome tutorial!
Thanks a lot for your efforts in porting all examples to the new version and writing that new tutorial!
I haven't read through everything in detail yet, but I found a paragraph that is not completely correct:
Where an exception occurs in a task, it should be trapped either in that task or in a task which is awaiting its completion. This ensures that the exception is not propagated to the scheduler. If this occurred the scheduler would stop running, passing the exception to the code which started the scheduler. Consequently, to avoid stopping the scheduler, tasks launched with asyncio.create_task() must trap any exceptions internally.
An exception only stops the scheduler if it occurs in the task created by *asyncio.run(task())* because this by definition only runs the scheduler until *task()* is finished.
If an exception occurs in a task created by *asyncio.create_task* then it will be handled by *Loop.default_exception_handler* and not stop the scheduler. Using a custom exception handler you can then log those uncaught exceptions.
Consequently starting the scheduler with *Loop.run_forever()* will result in the scheduler never being stopped (unless there is a bug in the scheduler itself).
Also it is therefore not neccessary to trap all exceptions. Actually the contrary is advisable in my opinion, at least for TimeoutErrors and CancelledErros. Reason: If a coroutine catches a cancellation error without raising it, the coroutine awaiting that task will never know what happened to the task it awaited.