Download as pdf or txt
Download as pdf or txt
You are on page 1of 83

Python Asyncio Interview Questions

Answers To 150+ Interview Questions On Asynchronous Programming and


Coroutines in Python

Jason Brownlee

2022
i

Praise for SuperFastPython


“I’m reading this article now, and it is really well made (simple, concise but
comprehensive). Thank you for the effort! Tech industry is going forward thanks
also by people like you that diffuse knowledge.”
– Gabriele Berselli, Python Developer.
“I enjoy your postings and intuitive writeups - keep up the good work”
– Martin Gay, Quantitative Developer at Lacima Group.
“Great work. I always enjoy reading your knowledge based articles”
– Janath Manohararaj, Director of Engineering.
“Great as always!!!”
– Jadranko Belusic, Software Developer at Crossvallia.
“Thank you for sharing your knowledge. Your tutorials are one of the best I’ve
read in years. Unfortunately, most authors, try to prove how clever they are and
fail to educate. Yours are very much different. I love the simplicity of the examples
on which more complex scenarios can be built on, but, the most important aspect
in my opinion, they are easy to understand. Thank you again for all the time and
effort spent on creating these tutorials.”
– Marius Rusu, Python Developer.
“Thanks for putting out excellent content Jason Brownlee, tis much appreciated”
– Bilal B., Senior Data Engineer.
“Thank you for sharing. I’ve learnt a lot from your tutorials, and, I am still doing,
thank you so much again. I wish you all the best.”
– Sehaba Amine, Research Intern at LIRIS.
“Wish I had this tutorial 7 yrs ago when I did my first multithreading software.
Awesome Jason”
– Leon Marusa, Big Data Solutions Project Leader at Elektro Celje.
“This is awesome”
– Subhayan Ghosh, Azure Data Engineer at Mercedes-Benz R&D.

i
ii

Copyright
© Copyright 2022 Jason Brownlee. All Rights Reserved.

Disclaimer
The information contained within this book is strictly for educational purposes. If you wish
to apply ideas contained in this book, you are taking full responsibility for your actions.
The author has made every effort to ensure the accuracy of the information within this
book was correct at time of publication. The author does not assume and hereby disclaims
any liability to any party for any loss, damage, or disruption caused by errors or omissions,
whether such errors or omissions result from accident, negligence, or any other cause.
No part of this book may be reproduced or transmitted in any form or by any means, electronic
or mechanical, recording or by any information storage and retrieval system, without written
permission from the author.

ii
iii

Preface
Python concurrency is deeply misunderstood.
Opinions vary from “Python does not support concurrency” to “Python concurrency is buggy”.
I created the website SuperFastPython.com to directly counter these misunderstandings.
Python asyncio can be tricky and confusing for beginners.
I was once asked: How would you evaluate whether a developer understands Python concur-
rency?
This book is my answer to the question for Python asyncio, including the asyncio module
and coroutines.
For a Python developer to be effective, they must know how to use the API, and more
importantly, when to use it.
This book was carefully crafted to provide a long series of questions I might ask in an interview
to check if a developer really understands Python asyncio and how to bring coroutines and
asynchronous programming to a project.
My hope is that together, we can make Python code run faster and change the community’s
opinions about Python concurrency.
Thank you for letting me guide you along this path.
Jason Brownlee, Ph.D.
SuperFastPython.com
2022.

iii
Contents

Copyright . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii

Introduction 1
Who Is This Book For? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Book Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
How to Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Which Python Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Asynchronous Programming 4
Q. What does asynchronous mean? . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Q. What is an asynchronous function call? . . . . . . . . . . . . . . . . . . . . . . 4
Q. What is asynchronous programming? . . . . . . . . . . . . . . . . . . . . . . . 4
Q. What are two ways we can run asynchronous tasks with threads? . . . . . . . . 5
Q. What are two ways we can run asynchronous tasks with processes? . . . . . . . 5
Q. What is another programming language where asynchronous I/O is popular? . 6
Q. What are 3 benefits of using asynchronous programming? . . . . . . . . . . . . 6
Q. What area is asynchronous programming most useful and why? . . . . . . . . . 6
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Coroutines 7
Q. What are coroutines? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Q. What type of multitasking is achieved using coroutines? . . . . . . . . . . . . . 7
Q. What was the precursor to coroutines in Python? . . . . . . . . . . . . . . . . 7
Q. How are coroutines different from threads? . . . . . . . . . . . . . . . . . . . . 8
Q. What does the async/await syntax mean? . . . . . . . . . . . . . . . . . . . . 8
Q. What 3 async expressions were added to Python? . . . . . . . . . . . . . . . . 8
Q. What does the await expression do? . . . . . . . . . . . . . . . . . . . . . . . 8
Q. How do you define a coroutine? . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Q. What is a coroutine function? . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Q. What does a coroutine function return? . . . . . . . . . . . . . . . . . . . . . . 9
Q. How is a coroutine suspended? . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Q. What executes and switches between coroutines? . . . . . . . . . . . . . . . . . 10
Q. How are coroutines and subroutines similar and different? . . . . . . . . . . . . 10

iv
Contents v

Q. How are coroutines and generators similar and different? . . . . . . . . . . . . 10


Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Asyncio 12
Q. What is the asyncio module? . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Q. What does asyncio stand for? . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Q. What are the two APIs in asyncio and when should each be used? . . . . . . . 12
Q. What is blocking I/O? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Q. What is non-blocking I/O? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Q. What types of non-blocking I/O does asyncio offer? . . . . . . . . . . . . . . . 13
Q. What are 3 reasons to use asyncio? . . . . . . . . . . . . . . . . . . . . . . . . 13
Q. Is asyncio affected by the GIL? . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Q. What are 5 applications where we could use asyncio for non-blocking I/O? . . 14
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Run Coroutines 15
Q. How do you start an asyncio program? . . . . . . . . . . . . . . . . . . . . . . 15
Q. How do you run a single coroutine from a coroutine? . . . . . . . . . . . . . . . 15
Q. What is an awaitable? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Q. What are 3 examples of awaitables? . . . . . . . . . . . . . . . . . . . . . . . . 16
Q. How can you sleep asynchronously in a coroutine? . . . . . . . . . . . . . . . . 16
Q. Why would we sleep for zero seconds in a coroutine? . . . . . . . . . . . . . . . 16
Q. Can two or more coroutines run at the same time in the same event loop? . . . 16
Q. What suspends and resumes coroutines? . . . . . . . . . . . . . . . . . . . . . 17
Q. How do we return a value from a coroutine? . . . . . . . . . . . . . . . . . . . 17
Q. What happens if we don’t execute a coroutine? . . . . . . . . . . . . . . . . . . 17
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Tasks 18
Q. What is an asyncio future? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Q. What is an asyncio task? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Q. How do you create a task? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Q. How can we get all running tasks? . . . . . . . . . . . . . . . . . . . . . . . . . 19
Q. How can we get the currently running task? . . . . . . . . . . . . . . . . . . . 19
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Working with Tasks 20


Q. How can we check if a task is still running? . . . . . . . . . . . . . . . . . . . . 20
Q. How can we check if a task is done? . . . . . . . . . . . . . . . . . . . . . . . . 20
Q. What are two ways to get the return value from a task? . . . . . . . . . . . . . 21
Q. What happens if we call the result() method and the task is not done? . . . 21
Q. What happens if we get the result from a task that was canceled? . . . . . . . 21
Q. How can we get an unhandled exception raised by a task? . . . . . . . . . . . . 22
Q. How can we add a done callback function to a task? . . . . . . . . . . . . . . . 22
Q. Can we remove a done callback function from a task? . . . . . . . . . . . . . . 22

v
Contents vi

Q. When is a done callback function called for a task? . . . . . . . . . . . . . . . 22


Q. How can we get the coroutine object for a task? . . . . . . . . . . . . . . . . . 23
Q. What are two ways to set the name of a task? . . . . . . . . . . . . . . . . . . 23
Q. Why would we set a name for a task? . . . . . . . . . . . . . . . . . . . . . . . 23
Q. How can we cancel a task and what happens to the task? . . . . . . . . . . . . 23
Q. How can we check if a task was canceled? . . . . . . . . . . . . . . . . . . . . . 24
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

Run Multiple Coroutines 25


Q. What does gather() do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Q. How do you provide a list of coroutines to gather()? . . . . . . . . . . . . . . 25
Q. What does gather() return? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Q. Do we have to await gather() for the provided coroutines to execute? . . . . . 26
Q. Can gather() take asyncio.Task objects as arguments? . . . . . . . . . . . . 26
Q. What happens if one coroutine passed to gather() raises an exception? . . . . 26
Q. How can we cancel all coroutines passed to gather()? . . . . . . . . . . . . . . 26
Q. How can we add a callback to all coroutines passed to gather()? . . . . . . . 27
Q. How can we retrieve exceptions raised by coroutines passed to gather()? . . . 27
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

Working with Coroutines 28


Q. How do you shield a Task or coroutine from cancellation? . . . . . . . . . . . . 28
Q. What happens if shielded Task is canceled or task? . . . . . . . . . . . . . . . 28
Q. What happens if we cancel the inner Task when using shield()? . . . . . . . 28
Q. How can we wait for a Task or coroutine with a timeout? . . . . . . . . . . . . 29
Q. What happens if the timeout is exceeded when calling wait_for()? . . . . . . 29
Q. Are tasks canceled after the timeout? . . . . . . . . . . . . . . . . . . . . . . . 29
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Waiting for Tasks 30


Q. How do we wait on a collection of tasks? . . . . . . . . . . . . . . . . . . . . . 30
Q. How do we wait on a collection of tasks with a timeout? . . . . . . . . . . . . . 30
Q. What happens if the timeout is exceeded when using wait()? . . . . . . . . . 31
Q. How do we wait for the first task to complete in a collection of tasks? . . . . . 31
Q. How do we wait for the first task to fail in a collection of tasks? . . . . . . . . 31
Q. How do we get task results in the order they are completed? . . . . . . . . . . 32
Q. What does the timeout argument do on the as_completed() function? . . . . 32
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

Blocking Tasks 33
Q. What is a blocking function? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Q. What happens if a coroutine calls a blocking function? . . . . . . . . . . . . . 33
Q. How we treat a blocking function like its non-blocking in asyncio? . . . . . . . 33
Q. How do we call blocking I/O-bound functions from asyncio? . . . . . . . . . . 34
Q. How do we call blocking CPU-bound functions from asyncio? . . . . . . . . . . 34

vi
Contents vii

Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Event Loop 36
Q. What is the asyncio event loop? . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Q. How do we get the event loop object? . . . . . . . . . . . . . . . . . . . . . . . 36
Q. How do we exit the event loop? . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Q. How can another thread schedule a coroutine in an event loop? . . . . . . . . . 37
Q. How are coroutines represented in the event loop? . . . . . . . . . . . . . . . . 37
Q. Is the event loop multithreaded? . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Advanced Tasks 39
Q. How could we get a task by its name? . . . . . . . . . . . . . . . . . . . . . . . 39
Q. How could we wait for all background tasks? . . . . . . . . . . . . . . . . . . . 39
Q. How can we run a coroutine after a delay? . . . . . . . . . . . . . . . . . . . . 40
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Iterators, Generators, and Context Managers 41


Q. What does async for do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Q. When would we use async for? . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Q. What two methods does an asynchronous iterator implement? . . . . . . . . . 42
Q. How would you write a simple asynchronous iterator and then iterate it? . . . 42
Q. What method is used to iterate one step of an asynchronous iterator? . . . . . 44
Q. What is an asynchronous generator? . . . . . . . . . . . . . . . . . . . . . . . . 44
Q. How is a coroutine function different from an asynchronous generator? . . . . . 44
Q. How would you write a simple asynchronous generator and then iterate it? . . 44
Q. How would you traverse an asynchronous generator in a list comprehension? . 45
Q. What is an await comprehension and give an example? . . . . . . . . . . . . . 45
Q. What does async with do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Q. What two methods must exist on an asynchronous context manager? . . . . . 46
Q. How would you write a simple asynchronous context manager and use it? . . . 46
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Synchronization Primitives 49
Q. What does coroutine-safe mean? . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Q. Can we get race conditions with coroutines? . . . . . . . . . . . . . . . . . . . 49
Q. Can we get deadlocks with coroutines? . . . . . . . . . . . . . . . . . . . . . . 50
Q. How can we use a mutex to protect a critical section from coroutines? . . . . . 51
Q. How can a coroutine check if a mutex is locked? . . . . . . . . . . . . . . . . . 52
Q. When might we use an Event? . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Q. How can we check if an Event is set? . . . . . . . . . . . . . . . . . . . . . . . 52
Q. How can a coroutine set an Event? . . . . . . . . . . . . . . . . . . . . . . . . 53
Q. What is an asyncio condition variable? . . . . . . . . . . . . . . . . . . . . . . 53
Q. How can we wait to be notified with an asyncio condition variable? . . . . . . 53
Q. How can we notify all coroutines waiting on a condition? . . . . . . . . . . . . 54

vii
Contents viii

Q. What is a semaphore? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Q. How can we configure a semaphore? . . . . . . . . . . . . . . . . . . . . . . . . 55
Q. How can we use a semaphore to limit access to a critical section? . . . . . . . . 55
Q. How can we use a semaphore like a mutex? . . . . . . . . . . . . . . . . . . . . 56
Q. What happens if a coroutine tries to acquire a semaphore that has no space? . 56
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

Queues 57
Q. Why would we use queues in an asyncio program? . . . . . . . . . . . . . . . . 57
Q. What 3 types of queues does asyncio provide? . . . . . . . . . . . . . . . . . . 57
Q. How can we define a fixed-sized queue? . . . . . . . . . . . . . . . . . . . . . . 57
Q. How do we add and remove items from a queue? . . . . . . . . . . . . . . . . . 58
Q. What happens if a coroutine adds an item to a queue that is full? . . . . . . . 58
Q. Why would we use the task_done() and join() methods? . . . . . . . . . . . 58
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Subprocesses 60
Q. How is running a command directly vs via the shell different? . . . . . . . . . . 60
Q. What are some common shells on Unix-based operating systems? . . . . . . . . 60
Q. How can we run a command directly from asyncio? . . . . . . . . . . . . . . . 60
Q. How can we run a command using the shell from asyncio? . . . . . . . . . . . . 61
Q. How do we interact with the subprocess running the command? . . . . . . . . 61
Q. Does the subprocess continue running if the asyncio event loop exits? . . . . . 61
Q. How can we read data from a subprocess? . . . . . . . . . . . . . . . . . . . . 61
Q. How can we write data to a subprocess? . . . . . . . . . . . . . . . . . . . . . . 62
Q. How can we stop a command running in a subprocess? . . . . . . . . . . . . . 62
Q. How can we wait for a subprocess to finish? . . . . . . . . . . . . . . . . . . . . 62
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

Streams 64
Q. What are 5 common application-layer protocols used on the internet? . . . . . 64
Q. What are 3 socket programs you could develop involving a web server? . . . . 64
Q. How can we open a TCP client connection? . . . . . . . . . . . . . . . . . . . . 64
Q. How can we open a TCP server connection? . . . . . . . . . . . . . . . . . . . 65
Q. How would we connect to a web server on port 80? . . . . . . . . . . . . . . . 65
Q. How would we connect to a web server running HTTPS on the regular port? . 66
Q. What are two ways to read lines from a StreamReader? . . . . . . . . . . . . . 66
Q. How can we write lines of data to a StreamWriter? . . . . . . . . . . . . . . . 66
Q. What is the drain() method and when would we use it? . . . . . . . . . . . . 67
Q. How do we close a connection? . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

Conclusions 69
Well Done! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Resources For Diving Deeper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

viii
Contents ix

Getting More Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

About the Author 71

Interview Questions Series 72

ix
Introduction

Asyncio provides a powerful framework for developing asynchronous programs in Python.


This includes changes to the Python language itself to support coroutines. It also includes
the framework provided by the asyncio module API for creating and running asynchronous
programs that use coroutine-based concurrency.
This book provides a carefully curated list of questions on Python asyncio, and the answers
expected from an experienced Python developer.
Before we dive into the questions, let’s take a look at what is coming with a breakdown of
this book.

Who Is This Book For?


Let’s make sure you’re in the right place.
Specifically, this book is for:
• Developers that want to learn asyncio with pointed questions.
• Developers that want to test their knowledge of the asyncio API and capabilities.
• Engineering managers that want to evaluate candidates for a job that requires Python
asyncio.
Next, let’s take a look at what this book will cover.

Book Overview
This book provides a large number of questions on what, how and when to use the Python
asyncio API.
The questions are divided into major topics, they are:
• Asynchronous Programming
• Coroutines
• Asyncio
• Run Coroutines
• Run Coroutines

1
Introduction 2

• Working with Tasks


• Run Multiple Coroutines
• Working with Coroutines
• Waiting for Tasks
• Blocking Tasks
• Event Loop
• Advanced Tasks
• Iterators, Generators, and Context Managers
• Synchronization Primitives
• Queues
• Subprocesses
• Streams
Each topic has a preamble summarizing the types of questions to expect for the topic and
why they are important.
Each question is listed in a heading format, so that you can read it and think about it for
self-study. This is followed by the answer.
Sometimes the answers will provide additional clarifying information and sample code.
Those questions that require a code example will provide a full code listing in the answer
that may be used as a guide.

How to Read
If you are a Python developer looking to learn asyncio, I recommend working through the
book sequentially from start-to-finish. You can pick and choose topics if you need to brush
up in specific areas.
If you are a Python developer looking to test your knowledge, I would recommend working
through questions one at a time. All questions are listed in the table of contents, which you
can use to test your knowledge. Record your answers, then compare your answers to the
answers provided later in the book.
If you are an engineering manager, I recommend selecting a sample of questions from the
book to ask in a technical interview. Perhaps select some general knowledge questions as
well as some questions that require a code listing that could be prepared on paper, a google
doc or whiteboard. The questions in the book may also be combined, remixed, or used to
inspire your own questions.

Which Python Version


All questions are based on Python version 3.9 or higher.

2
Introduction 3

Getting Help
The questions and answers are intentionally terse.
This is not a jump-start guide or master class. Instead, these are interview questions that
may be used for self-study or to test engineering capabilities.
Nevertheless, sometimes we need a little extra help.
A list of further reading resources is provided at the end of each section. These can be helpful
if you are interested in learning more about the topic covered, such as fine grained details of
the standard library and API functions used.
Finally, if you ever have questions about the interview questions or answers in this book, you
can contact me any time and I will do my best to help. My contact details are provided at
the end of the book.
Now that we know what’s coming, let’s get started.

Next
Next is the first topic where we will take a closer look at asynchronous programming.

3
Asynchronous Programming

This section covers questions on asynchronous programming in Python.


You must know the basics of asynchronous programming before using coroutines and the
asyncio module.
Let’s get started.

Q. What does asynchronous mean?


Asynchronous means not at the same time, as opposed to synchronous or at the same time.
When programming, asynchronous means that the action is issued or requested, although
not performed at the time of the request. It is performed later.

Q. What is an asynchronous function call?


An asynchronous function call will issue the request to make the function call and will not
wait around for the call to complete. We can choose to check on the status or result of the
function call later.
The function call will happen somehow and at some time, in the background, and the program
can perform other tasks or respond to other events.
We don’t have control over how or when the request is handled, only that we would like it
handled while the program does other things.
Issuing an asynchronous function call often results in some handle on the request that the
caller can use to check on the status of the call or get results. This is often called a future.

Q. What is asynchronous programming?


Issuing asynchronous tasks and making asynchronous function calls is referred to as asyn-
chronous programming.
Asynchronous programming is primarily used with non-blocking I/O, such as reading and
writing from socket connections with other processes or other systems.

4
Asynchronous Programming 5

Q. What are two ways we can run asynchronous tasks


with threads?
We can issue asynchronous tasks with threads using the multiprocessing.pool.ThreadPool
class and the concurrent.futures.ThreadPoolExecutor class.
The ThreadPool class provides methods for issuing asynchronous tasks, such as map_async()
and apply_async(). These methods take the name of a function and return an AsyncResult
as a handle on the task.
For example:
...
# issue an async function call
async_result = threadpool.apply_async(target_func)

The ThreadPoolExecutor provides the submit() method for issuing asynchronous tasks and
returning a Future that provides a handle on the task.
For example:
...
# issue an async function call
future = executor.submit(target_func)

Q. What are two ways we can run asynchronous tasks


with processes?
We can issue asynchronous tasks with threads using the multiprocessing.pool.Pool class
and the concurrent.futures.ProcessPoolExecutor class.
The Pool class provides methods for issuing asynchronous tasks, such as map_async() and
apply_async(). These methods take the name of a function and return an AsyncResult as
a handle on the task.
For example:
...
# issue an async function call
async_result = pool.apply_async(target_func)

The ProcessPoolExecutor provides the submit() method for issuing asynchronous tasks
and returning a Future that provides a handle on the task.
For example:
...
# issue an async function call
future = executor.submit(target_func)

5
Asynchronous Programming 6

Q. What is another programming language where asyn-


chronous I/O is popular?
Asynchronous programming is very popular with JavaScript.

Q. What are 3 benefits of using asynchronous program-


ming?
Three benefits of asynchronous programming includes:
• Scalability. Asynchronous programs can often scale significantly more than alternate
approaches to concurrency, such as threads. For example, an asynchronous program may
have tens or hundreds of thousands of concurrent tasks or open connections, whereas a
multithreaded program may be limited to hundreds or thousands.
• Responsiveness. Asynchronous programs can be more responsive by allowing results
to be handled as they become available, while the program carries on with other
activities.
• Simplicity. Asynchronous programs may have less code or simpler solutions than
some other approaches to concurrency, such as threads.

Q. What area is asynchronous programming most useful


and why?
Asynchronous programming is most useful when paired with non-blocking I/O.
It allows the program to handle I/O asynchronously when needed, allowing the program to
remain responsive and continue on with other activities.

Further Reading
This section provides additional resources on or related to this topic.
• Asynchrony (computer programming), Wikipedia.
https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)
• Asynchronous I/O, Wikipedia.
https://en.wikipedia.org/wiki/Asynchronous_I/O
• PEP 3156 – Asynchronous IO Support Rebooted: the asyncio Module.
https://peps.python.org/pep-3156/
• concurrent.futures - Launching parallel tasks.
https://docs.python.org/3/library/concurrent.futures.html

6
Coroutines

This section covers questions on coroutines in Python.


Coroutines are the fundamental unit of concurrency in asyncio programs and we must know
what they are, how they work, and how to use them.
Let’s get started.

Q. What are coroutines?


A coroutine is a function that can be suspended and resumed.
It is often defined as a generalized subroutine.
A subroutine can be executed, starting at one point and finishing at another point. Whereas, a
coroutine can be executed then suspended, and resumed many times before finally terminating.
Specifically, coroutines have control over when exactly they suspend their execution.

Q. What type of multitasking is achieved using corou-


tines?
Coroutines control when they are suspended and when they resume.
This is a type of multitasking called cooperative multitasking.
It can be contrasted to the multitasking used by threads called preemptive multitasking.
This is where the operating system chooses which thread can run and for how long, before
switching to the next thread.

Q. What was the precursor to coroutines in Python?


The precursor to coroutines in Python were generators.
Generators are functions that have at least one yield expression.
Generators can be suspended and resumed although are limited to a specific use case,
e.g. yielding values via iteration.

7
Coroutines 8

Python extended generators to support coroutines as first-class objects.

Q. How are coroutines different from threads?


Both threads and coroutines are units of concurrency and both are suited to I/O bound tasks.
They are different in 5 main ways:
• Threads are heavyweight and represented by a threading.Thread object compared to
coroutines that are functions.
• Threads are slower to start and use more memory (perhaps twice as much) compared
to coroutines.
• Threads are allocated, created, and managed by the operating system, and coroutines
are managed in software via an event loop.
• Threads multitask using preemptive multitasking, and coroutines multitask using
cooperative multitasking.
• Threads are suited for blocking I/O tasks whereas coroutines are suited to non-blocking
I/O tasks.

Q. What does the async/await syntax mean?


Async/await refers to a pattern used for asynchronous programming.
Generally, async defines a coroutine, and await suspends or yields execution to another
coroutine from within a coroutine.
The pattern is used for asynchronous programming in many popular programming languages,
such as JavaScript, Swift, Rust, and Python.
The async/await syntax was introduced in Python version 3.5.

Q. What 3 async expressions were added to Python?


Three async expressions were added to Python for working with coroutines
They were:
• async def for defining a coroutine.
• async for for traversing an asynchronous iterable.
• async with for using an asynchronous context manager.
An await expression was also added, but this is not an async expression.

Q. What does the await expression do?


The await expression takes an awaitable.

8
Coroutines 9

It suspends the caller until the awaitable is resolved, e.g. is done.


If a coroutine is provided, then the caller is suspended until the coroutine is executed and
returns.
The await expression is like the yield expression in generators, except it is more general.

Q. How do you define a coroutine?


A coroutine is defined using the async def expression.
This is an extension of the def expression for defining functions.
For example:
# define a coroutine
async def custom_coro():
print('hi there')

Q. What is a coroutine function?


A coroutine function is a function defined using the async def expression.
It is different from a function that is defined using the def expression.

Q. What does a coroutine function return?


A coroutine function returns a coroutine object.
Calling a coroutine function does not execute the body of the function. Instead, it creates
and returns a coroutine object from the coroutine function.
For example:
...
# create a coroutine object
coro = custom_coro()

Q. How is a coroutine suspended?


A coroutine suspends using the await expression.
For example:
...
# suspend until the awaitable is resolved
await awaitable

9
Coroutines 10

Q. What executes and switches between coroutines?


Coroutines are executed and managed via the asyncio event loop.
The event loop is a software runtime in the Python standard library for running asyncio
programs.
This is unlike threads that are managed, executed, and switched using the underlying
operating system.

Q. How are coroutines and subroutines similar and dif-


ferent?
A subroutine is a discrete module of expressions that is assigned a name, may take arguments,
and may return a value.
A subroutine is executed, runs through the expressions, and returns somehow.
A coroutine is a generalization of a subroutine. This means that a subroutine is a special
type of coroutine.
A coroutine is like a subroutine in many ways, such as:
• They both are discrete named modules of expressions.
• They both can take arguments, or not.
• They both can return a value, or not.
The main difference is that a coroutine has the opportunity to suspend and resume its
execution before exiting.
Both coroutines and subroutines can call other examples of themselves. A subroutine can
call other subroutines. A coroutine executes other coroutines. However, a coroutine can also
execute other subroutines.

Q. How are coroutines and generators similar and dif-


ferent?
Generators can only be used to yield values in loops, whereas coroutines are more general
and can be used more broadly for asynchronous programming.
A generator is a special function that can suspend its execution.
A generator function can be defined like a normal function although it uses a yield expression
at the point it will suspend its execution and return a value.
A generator function will return a generator iterator object that can be traversed, such
as via a for-loop. Each time the generator is executed, it runs from the last point it was
suspended to the next yield expression.

10
Coroutines 11

A coroutine can suspend or yield to another coroutine using an await expression. It will
then resume from this point once the awaited coroutine has been completed.
We might think of a generator as a special type of coroutine and cooperative multitasking
used in loops.

Further Reading
This section provides additional resources on or related to this topic.
• Coroutine, Wikipedia.
https://en.wikipedia.org/wiki/Coroutine
• Async/await, Wikipedia.
https://en.wikipedia.org/wiki/Async/await
• PEP 492 – Coroutines with async and await syntax.
https://peps.python.org/pep-0492/
• PEP 3156 – Asynchronous IO Support Rebooted: the asyncio Module.
https://peps.python.org/pep-3156/
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html

11
Asyncio

This section covers questions on the asyncio module in Python.


The asyncio module is a central part of asynchronous programming in Python and we must
know what it provides and how to use it.
Let’s get started.

Q. What is the asyncio module?


The asyncio module is part of the Python standard library.
It provides functions and objects for developing asynchronous programs using coroutines in
Python.

Q. What does asyncio stand for?


Asyncio is a contraction of asynchronous input/output or asynchronous I/O.

Q. What are the two APIs in asyncio and when should


each be used?
The asyncio module provides two APIs, they are:
• High-level API for application developers.
• Low-level API for framework and library developers.
Generally, we should use the high-level API.

Q. What is blocking I/O?


Blocking I/O refers to function calls for reading and writing from a resource that waits until
the call is complete.
For example, the resource may be a network socket, a file, or a device.

12
Asyncio 13

A blocking read would involve requesting data and waiting for the data to arrive before
resuming the application.
It is called blocking because the execution of the program (e.g. thread or coroutine) cannot
proceed until the call returns.

Q. What is non-blocking I/O?


Non-blocking I/O is a way of performing I/O where reads and writes are requested, although
performed asynchronously. The caller does not need to wait for the operation to complete
before returning.
The read and write operations are performed somehow (e.g. by the underlying operating
system or systems built upon it), and the status of the action and/or data is retrieved by the
caller later, once available, or when the caller is ready.
The combination of non-blocking I/O with asynchronous programming is so common that it
is referred to by the shorthand of asynchronous I/O.

Q. What types of non-blocking I/O does asyncio offer?


Asyncio offers non-blocking I/O with subprocesses and streams.
Subprocesses are used for executing commands on the system.
Streams are used for network programming by opening TCP sockets connections, either as a
client or server.

Q. What are 3 reasons to use asyncio?


Three reasons to use asyncio include:
• Use asyncio in order to adopt coroutines in your program.
• Use asyncio in order to use the asynchronous programming paradigm.
• Use asyncio in order to use non-blocking I/O.

Q. Is asyncio affected by the GIL?


No.
The GIL or Global Interpreter Lock is a mutex lock that ensures that only one thread can
run at a time.
Coroutines run within a single thread and therefore are not impacted by the GIL.

13
Asyncio 14

Q. What are 5 applications where we could use asyncio


for non-blocking I/O?
Five applications where we could use asyncio include:
• Scrape data from webpages.
• Download files from a server.
• Check the status of game servers.
• Crawl a website, check errors and report statistics.
• Interact with social media APIs, e.g. Twitter.

Further Reading
This section provides additional resources on or related to this topic.
• I/O bound, Wikipedia.
https://en.wikipedia.org/wiki/I/O_bound
• Asynchronous I/O, Wikipedia.
https://en.wikipedia.org/wiki/Asynchronous_I/O
• PEP 3156 – Asynchronous IO Support Rebooted: the asyncio Module.
https://peps.python.org/pep-3156/
• Python Global Interpreter Lock, Python Wiki.
https://wiki.python.org/moin/GlobalInterpreterLock
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html

14
Run Coroutines

This section covers questions on running coroutines.


Given that coroutines are central to asyncio programs, we must know how to run them and
how they behave when running.
Let’s get started.

Q. How do you start an asyncio program?


An asyncio program can be started by calling the asyncio.run() function and pass in a
coroutine to execute.
For example:
...
# start an asyncio program
asyncio.run(coro())

Q. How do you run a single coroutine from a coroutine?


A coroutine can run another coroutine by awaiting it.
For example:
...
# suspend and run another coroutine
await other_coro()

A coroutine can also schedule a coroutine to run independently via the asyncio.create_task()
function.
For example:
...
# execute the coroutine independently
task = asyncio.create_task(other_coro())

15
Run Coroutines 16

Q. What is an awaitable?
An awaitable refers to a Python object that can be awaited via the await expression.
Technically, it is an object that implements the __await__() magic method.

Q. What are 3 examples of awaitables?


Three examples of awaitables include:
• asyncio.Future
• asyncio.Task
• coroutine

Q. How can you sleep asynchronously in a coroutine?


A coroutine can sleep asynchronously by calling the asyncio.sleep() function and specifying
the number of seconds.
This is a coroutine function that must be awaited.
For example:
...
# sleep
await asyncio.sleep(10)

Q. Why would we sleep for zero seconds in a coroutine?


A coroutine would sleep for zero seconds to allow scheduled tasks and other coroutines an
opportunity to run.
For example:
...
# allow other tasks to run
await asyncio.sleep(0)

Q. Can two or more coroutines run at the same time in


the same event loop?
No.
An event loop can only execute one coroutine at a time.
All other coroutines must be suspended in order for a coroutine to execute.

16
Run Coroutines 17

Q. What suspends and resumes coroutines?


The asyncio event loop is responsible for suspending and resuming coroutines in an asyncio
program.

Q. How do we return a value from a coroutine?


We can return a value from a coroutine by defining a coroutine that returns a value via the
return expression.
The caller can retrieve the return value from a coroutine by assigning it as part of an await
expression.
For example:
...
# await another coroutine and retrieve the return value
value = await other_coro()

Q. What happens if we don’t execute a coroutine?


If a coroutine object is created and not executed, a RuntimeWarning is reported highlighting
that the coroutine was never awaited.
For example:
RuntimeWarning: coroutine '' was never awaited

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

17
Tasks

This section covers questions on asyncio tasks.


Tasks are a large part of asynchronous programming in asyncio, meaning we have to know a
lot about them.
Let’s get started.

Q. What is an asyncio future?


A future is an instance of the asyncio.Future class.
This class is part of the low-level asyncio API and is generally not used directly.
It is the base __awaitable__ type and the base class for the asyncio.Task class.

Q. What is an asyncio task?


An asyncio task is an instance of the asyncio.Task class.
Coroutines running in the asyncio event loop are managed as tasks.

Q. How do you create a task?


A task can be created from a coroutine via the asyncio.create_task() function.
This function takes a coroutine object as an argument, wraps the coroutine in an
asyncio.Task, schedules the task for execution in the event loop, then returns the
asyncio.Task object.
For example:
...
# create task and schedule it for execution
task = asyncio.create_task(other_coro())

18
Tasks 19

Q. How can we get all running tasks?


We can get all tasks running in an asyncio event loop via the asyncio.all_tasks() function.
This returns a set of all tasks that are currently running, including the current task.
For example:
...
# get all running tasks
tasks = asyncio.all_tasks()

Q. How can we get the currently running task?


We can get the currently running task via the asyncio.current_task() function.
This function returns the asyncio event loop’s asyncio.Task object for the currently executing
task or coroutine.
For example:
...
# get the current task
task = asyncio.current_task()

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html
• Asyncio Futures.
https://docs.python.org/3/library/asyncio-future.html

19
Working with Tasks

This section covers questions on asyncio tasks.


Tasks are a large part of asynchronous programming in asyncio, meaning we have to know a
lot about them.
Let’s get started.

Q. How can we check if a task is still running?


We can check if a task is currently running by calling the done() method.
If it returns False, then the task is still running, otherwise, it will return True indicating
that the task is done.
For example:
...
# check if a task is still running
if not task.done():
# ...

Q. How can we check if a task is done?


We can check if a task is finished by calling the done() method.
If you return True, the task is done, otherwise, the task is still running.
For example:
...
# check if the task is done
if task.done():
# ...

20
Working with Tasks 21

Q. What are two ways to get the return value from a


task?
The two ways to get a return value from a task are by awaiting it and by calling the result()
method.
For example:
...
# get the return value from a task
value = await task

And, for example:


...
# get the return value from a task
value = task.result()

The result can only be retrieved if the task is done.

Q. What happens if we call the result() method and


the task is not done?
If the result() method is called and the task is not done, then an InvalidStateError
exception is raised.
Therefore, it is a good idea to check if a task is done before calling the result() method,
and if it is not, then await it.
For example:
...
# check if a task is done
if not task.done():
await task
# get the result
result = task.result()

Q. What happens if we get the result from a task that


was canceled?
If we try to retrieve the result or return value from a task that was canceled, then a
CancelledError exception will be raised.

21
Working with Tasks 22

Q. How can we get an unhandled exception raised by a


task?
We can retrieve an unhandled exception from a task by calling the exception() method.
The exception can only be retrieved if the task is done.
For example:
...
# get the unhandled exception raised in a task
exception = task.exception()

Q. How can we add a done callback function to a task?


We can add a done callback function to a task via the add_done_callback() method.
The method takes the name of the function and must receive the task as an argument.
For example:
# callback function
def handler(task):
# ...

...
# add a done callback function
task.add_done_callback(handler)

Q. Can we remove a done callback function from a task?


A done callback function can be removed from a task via the remove_done_callback()
method.
For example:
...
# remove a done callback function
task.remove_done_callback(handler)

Q. When is a done callback function called for a task?


The done callback function is called after the task is done.
This includes:
• The task completes normally.
• The task fails with an unhandled exception.

22
Working with Tasks 23

• The task is canceled.


Once the task is done, each done callback function will be executed in turn.

Q. How can we get the coroutine object for a task?


We can retrieve the coroutine object for a task via the get_coro() method.
For example:
...
# get the coroutine from a task
coro = task.get_coro()

Q. What are two ways to set the name of a task?


The two ways to set the name of a task are via an argument to the asyncio.create_task()
function when the task is created and via the set_name() method on the task.
For example:
...
# set the name when creating the task
task = asyncio.create_task(coro(), name='CustomTask')

And, for example:


...
# set the name of the task
task.set_name('CustomTask')

Q. Why would we set a name for a task?


It can be helpful to set the name of the task for task introspection.
For example:
• To report the task name in log messages.
• To report the task name in error and warning messages.
• To perform activities conditional on the name of the task.

Q. How can we cancel a task and what happens to the


task?
A task can be canceled by calling the cancel() method.
This will return True if the task can be canceled, or False otherwise.

23
Working with Tasks 24

Only a task that is not done can be canceled.


For example:
...
# cancel the task
was_cancelled = task.cancel()

The task will not be canceled until it is given an opportunity to run.


A CancelledError exception will be raised in the task. If the task handles the
CancelledError exception, the task will not cancel.
Therefore, even if the cancel() method returns True, the task may not be canceled.

Q. How can we check if a task was canceled?


We can check if a task was canceled by calling the cancelled() method.
If the cancelled() method returns True, the task was canceled, otherwise, it was not.

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

24
Run Multiple Coroutines

This section covers questions regarding the running of many coroutines and tasks concurrently.
A large part of asyncio programs is often dedicated to running many tasks concurrently,
therefore, it is essential that we know how.
Let’s get started.

Q. What does gather() do?


The asyncio.gather() function will execute awaitables and return an iterable of return
values.
It is typically used to execute multiple coroutines concurrently.
For example:
...
# run many coroutines concurrently
results = await asyncio.gather(coro1(), coro2())

Q. How do you provide a list of coroutines to gather()?


A list of coroutines can be provided to the asyncio.gather() function using the star operator,
also called the asterisk operator (*).
For example:
...
# run a list of coroutines
results = await asyncio.gather(*coro_list)

Q. What does gather() return?


The asyncio.gather() is a coroutine function.
It returns an asyncio.Future that represents a group of tasks.
This future can be awaited or scheduled as a task.

25
Run Multiple Coroutines 26

The result of asyncio.gather() is an iterable of return values from all awaitables passed as
an argument.
For example:
...
# get future for group of coroutines
future = asyncio.gather(coro1(), coro2(), coro3())
# execute the future
await future
# get the iterable of return values
values = future.result()

Q. Do we have to await gather() for the provided corou-


tines to execute?
No.
Coroutines passed to asyncio.gather() are automatically scheduled as independent tasks.

Q. Can gather() take asyncio.Task objects as argu-


ments?
Yes.
The asyncio.gather() function takes awaitables, including coroutines, Task, and Future
objects.

Q. What happens if one coroutine passed to gather()


raises an exception?
By default, when a coroutine is passed to asyncio.gather() fails with an unhandled
exception, the exception is propagated back to the task awaiting the group.

Q. How can we cancel all coroutines passed to gather()?


We can cancel a group of coroutines passed to asyncio.gather() by retrieving the
asyncio.Future returned from the asyncio.gather() coroutine function and calling the
cancel() method.
For example:
...
# get future for group of coroutines

26
Run Multiple Coroutines 27

future = asyncio.gather(coro1(), coro2(), coro3())


# cancel the group
future.cancel()

Q. How can we add a callback to all coroutines passed


to gather()?
We can add a done callback function for a group of coroutines passed to asyncio.gather()
by retrieving the asyncio.Future object returned from the coroutine function and call the
add_done_callback() method.
For example:
...
# get future for group of coroutines
future = asyncio.gather(coro1(), coro2(), coro3())
# add a done callback function
future.add_done_callback(handler)

Q. How can we retrieve exceptions raised by coroutines


passed to gather()?
We can retrieve unhandled exceptions raised by coroutines in a call to asyncio.gather() by
setting the return_exceptions argument to True.
This will make exceptions available in the iterable of return values.
For example:
...
# execute coroutines and retrieve any exceptions
results = await asyncio.gather(coro1(), coro2(),
return_exceptions=True)

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

27
Working with Coroutines

This section covers questions related to working with coroutines.


It is important to know the details of shielding coroutines from cancellation and executing
coroutines with a fixed timeout.
Let’s get started.

Q. How do you shield a Task or coroutine from cancella-


tion?
A coroutine can be shielded from cancellation by calling the asyncio.shield() function and
passing around the awaitable that is returned.
For example:
...
# get an awaitable that is shielded from cancellation
awaitable = asyncio.shield(coro())

Q. What happens if shielded Task is canceled or task?


The awaitable that wraps the shielded Task or coroutine is canceled.
This has no effect on the inner awaitable.

Q. What happens if we cancel the inner Task when using


shield()?
If the inner task or coroutine is canceled, then the shielded awaitable is also canceled.
The CancelledError exception raised in the inner Task is propagated to the outer awaitable
and to any callers awaiting it.

28
Working with Coroutines 29

Q. How can we wait for a Task or coroutine with a


timeout?
We can wait for a Task or coroutine with a timeout via the asyncio.wait_for() function.
The function takes an awaitable and a timeout in seconds.
For example:
...
# execute a coroutine with a timeout
result = await asyncio.wait_for(coro(), 10)

Q. What happens if the timeout is exceeded when calling


wait_for()?
If the timeout is exceeded when calling asyncio.wait_for() then the task is canceled and
a TimeoutError is raised in the awaiting caller, which may need to be handled.
For example:
...
try:
# execute a coroutine with a timeout
result = await asyncio.wait_for(coro(), 10)
except TimeoutError:
# ...

Q. Are tasks canceled after the timeout?


Yes.
The awaitable passed to the asyncio.wait_for() function is canceled if the timeout is
exceeded before the awaitable is done.

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

29
Waiting for Tasks

This section covers questions related to waiting for tasks.


A large part of executing many coroutines concurrently involves waiting for those tasks to
complete.
Let’s get started.

Q. How do we wait on a collection of tasks?


We can wait on a collection of tasks using the asyncio.wait() function.
This is a coroutine function that must be awaited and will return once all provided tasks are
done.
For example:
...
# wait for all tasks to complete
await asyncio.wait(tasks)

Q. How do we wait on a collection of tasks with a time-


out?
We can wait for a collection of tasks to complete by using the asyncio.wait() function and
specify the timeout argument in seconds.
This is a coroutine function that will suspend until the provided tasks are done or the timeout
elapses.
For example:
...
# wait for all tasks to complete with a timeout
await asyncio.wait(tasks, timeout=10)

30
Waiting for Tasks 31

Q. What happens if the timeout is exceeded when using


wait()?
If a timeout argument is specified to the asyncio.wait() function and it elapses before all
tasks are done, then the asyncio.wait() function will return.
A TimeoutError exception is not raised and the tasks are not canceled.

Q. How do we wait for the first task to complete in a


collection of tasks?
We can wait for the first task in a collection to complete by calling the asyncio.wait()
function and specifying the return_when argument to asyncio.FIRST_COMPLETED.
This will return a tuple where the first element is a set containing the first task to be done,
and the second set contains all tasks that are not yet done.
For example:
...
# get the first task to complete
first, rest = await asyncio.wait(tasks,
return_when=asyncio.FIRST_COMPLETED)

Q. How do we wait for the first task to fail in a collection


of tasks?
We can wait for the first task in a collection to fail with an exception by call-
ing the asyncio.wait() function and specifying the return_when argument to
asyncio.FIRST_EXCEPTION.
This will return a tuple where the first element is a set containing the first task to have failed,
and the second set contains all tasks that are not yet done.
If no task fails with an exception, then the call will be suspended until all tasks are complete
and the first element of the returned tuple will contain all tasks.
For example:
...
# get the first task to fail with an exception
first, rest = await asyncio.wait(tasks,
return_when=asyncio.FIRST_EXCEPTION)

31
Waiting for Tasks 32

Q. How do we get task results in the order they are


completed?
We can get task results in the order that the tasks are completed using the
asyncio.as_completed() function.
This function takes a collection of tasks and returns an iterable. The iterable will return
coroutines that if awaited will yield the result of the next task to complete.
For example:
...
# traverse tasks in completion order
for task in asyncio.as_completed(tasks):
# get the next result
result = await task

Q. What does the timeout argument do on the


as_completed() function?
The timeout argument on the asyncio.as_completed() specifies the number of seconds to
wait for all tasks to complete.
If the timeout elapses before all tasks are complete, then a TimeoutError exception is raised.

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

32
Blocking Tasks

This section covers questions related to executing blocking calls from asyncio programs.
There are many blocking calls we may need to complete within an asyncio program, such
as working with files, executing CPU-bound tasks, and working with legacy APIs. It is
important to know how to correctly handle blocking calls in asyncio.
Let’s get started.

Q. What is a blocking function?


A blocking function is a function that does not return until it is complete.
In concurrent programming, it refers to a function that halts the progress of the program
until it has been completed.
Examples of blocking functions may include reading or writing from a resource like a file or
performing a CPU-intensive operation such as an elaborate mathematical calculation.

Q. What happens if a coroutine calls a blocking function?


When a blocking function call is made in an asyncio program, it prevents the asyncio program
from progressing.
When a blocking call is running, the calling coroutine does not suspend and tasks are not
able to run in the background.
This is undesirable.

Q. How we treat a blocking function like its non-blocking


in asyncio?
We can execute a blocking function call asynchronously using the asyncio.to_thread()
function and specify the function name and arguments.
This will execute the function in a new thread and simulate an asynchronous task.

33
Blocking Tasks 34

For example:
...
# execute a blocking call asynchronously in asyncio
await asyncio.to_thread(task)

Q. How do we call blocking I/O-bound functions from


asyncio?
Blocking I/O function calls, such as reading or writing a file can be treated asynchronously
using the asyncio.to_thread() function.
The reason is that the asyncio.to_thread() function will use a ThreadPoolExecutor
automatically behind the scenes, which is appropriate for executing I/O-bound tasks in
Python.

Q. How do we call blocking CPU-bound functions from


asyncio?
A CPU-bound task can be executed in asyncio by simulating an asynchronous function call.
This can be achieved by first getting access to the current event loop via the
asyncio.get_running_loop() function, then create an instance of the ProcessPoolExecutor
and pass it along with the target function to the loop.run_in_executor() method on the
event loop.
The ProcessPoolExecutor is appropriate for CPU-bound tasks because it will execute those
tasks in a separate process that is not limited by the global interpreter lock.
The loop.run_in_executor() method allows a blocking function call to be executed using
a provided Executor, such as the ThreadPoolExecutor or the ProcessPoolExecutor.
For example:
...
# get the event loop
loop = asyncio.get_running_loop()
# create the executor
with ProcessPoolExecutor() as exe:
# execute the task asynchronously
await loop.run_in_executor(exe, task)

Further Reading
This section provides additional resources on or related to this topic.

34
Blocking Tasks 35

• I/O bound, Wikipedia.


https://en.wikipedia.org/wiki/I/O_bound
• CPU-bound, Wikipedia.
https://en.wikipedia.org/wiki/CPU-bound
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

35
Event Loop

This section covers questions related to the asyncio event loop.


The asyncio event loop is an important part of every asyncio program and it is critical to
know details about what it does and how to access it.
Let’s get started.

Q. What is the asyncio event loop?


The asyncio event loop is the heart of every asyncio program.
It is responsible for:
• Executing tasks.
• Executing callback functions.
• Performing network I/O operations.
• Running subprocesses

Q. How do we get the event loop object?


We can get access to the current event loop object via the get_running_loop() function.
For example:
...
# get the event loop
loop = asyncio.get_running_loop()

The asyncio.get_event_loop() can also be used and will create an event loop if no loop is
running.
...
# get the event loop
loop = asyncio.get_event_loop()

36
Event Loop 37

Q. How do we exit the event loop?


We can exit the event loop by returning from the main coroutine.
This is the coroutine passed to the asyncio.run() function.
This can be achieved by letting the main coroutine end normally, explicitly returning, or
raising an unhandled exception.
From outside of the event loop, the event loop can be shut down by calling the close()
method. This method cannot be called reliably from within an asyncio coroutine.
For example:
...
# get the event loop
loop = asyncio.get_running_loop()
# close the event loop
loop.close()

Q. How can another thread schedule a coroutine in an


event loop?
The run_coroutine_threadsafe() function can be used by another thread to safely schedule
a coroutine in the event loop.
It requires access to the target event loop, which is passed as an argument to the function.
For example:
...
# schedule a coroutine in a given event loop
asyncio.run_coroutine_threadsafe(coro(), loop)

Q. How are coroutines represented in the event loop?


Coroutines are represented in the asyncio event loop as asyncio.Task objects.
A Task object wraps a coroutine and provides methods for managing the coroutine, including
getting the result and canceling its execution.

Q. Is the event loop multithreaded?


No.
The asyncio event loop runs in a single thread.
A Python thread may only execute a single event loop.

37
Event Loop 38

The event loop will block the thread until it is finished.

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Asyncio Event Loop.
https://docs.python.org/3/library/asyncio-eventloop.html

38
Advanced Tasks

This section covers questions on some advanced usage of tasks.


It is important to know how to skillfully use tasks in asyncio programs.
Let’s get started.

Q. How could we get a task by its name?


Tasks can be assigned a meaningful name either via the name argument to the
asyncio.create_task() function or via the set_name() method.
A task can be retrieved by name by first getting a list of all running tasks via the
asyncio.all_tasks() function, then checking the name of each.
For example:
...
# search for a task with a give name
for task in asyncio.all_tasks():
if task.get_name() == 'MyTask':
print(f'Found: {task}')

Q. How could we wait for all background tasks?


We can wait for all tasks that are running in the background by first getting the set of
all running tasks via the asyncio.all_tasks() function, removing the current task, then
calling asyncio.wait() on the set of remaining tasks.
For example:
...
# get all tasks
tasks = asyncio.all_tasks()
# get the current task
current = asyncio.current_task()
# remove the current task
tasks.remove(current)

39
Advanced Tasks 40

# wait on all remaining background tasks


await asyncio.wait(tasks)

Q. How can we run a coroutine after a delay?


We can run a coroutine after a delay by defining a new coroutine that sleeps for a given delay
before awaiting the provided coroutine.
For example:
# start another coroutine after a delay in seconds
async def delay(coro, seconds):
# suspend for a time limit in seconds
await asyncio.sleep(seconds)
# execute the other coroutine
await coro

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Coroutines and Tasks.
https://docs.python.org/3/library/asyncio-task.html

40
Iterators, Generators, and Context
Managers

This section covers questions on asynchronous iterators, asynchronous generators, and asyn-
chronous context managers.
These are an often overlooked area of asynchronous programming in Python, but critical to
application developers.
Let’s get started.

Q. What does async for do?


The async for expression is used to traverse an asynchronous iterator.
It is an asynchronous for-loop statement.
Internally, the async for loop will automatically resolve or await each awaitable, scheduling
coroutines as needed.
Because it is a for-loop, it assumes, although does not require, that each awaitable being
traversed yields a return value.
For example:
...
# traverse an asynchronous iterator
async for item in async_iterator:
print(item)

This does not execute the for-loop in parallel. The asyncio event loop is unable to execute
more than one coroutine at a time within a Python thread.

Q. When would we use async for?


We can use the async for expression with an asynchronous iterator or an asynchronous
generator.

41
Iterators, Generators, and Context Managers 42

Q. What two methods does an asynchronous iterator


implement?
An asynchronous iterator is an object that implements the __aiter__() and __anext__()
methods.
The __aiter__() method must return an instance of the iterator.
The __anext__() method must return an awaitable that steps the iterator.

Q. How would you write a simple asynchronous iterator


and then iterate it?
We can define an asynchronous iterator by defining a class that implements the __aiter__()
and __anext__() methods.
Importantly, because the __anext__() function must return an awaitable, it must be defined
using the async def expression.
When the iteration is complete, the __anext__() method must raise a StopAsyncIteration
exception.
For example:
# define an asynchronous iterator
class AsyncIterator():
# constructor, define some state
def __init__(self):
self.counter = 0

# create an instance of the iterator


def __aiter__(self):
return self

# return the next awaitable


async def __anext__(self):
# check for no further items
if self.counter >= 10:
raise StopAsyncIteration
# increment the counter
self.counter += 1
# simulate work
await asyncio.sleep(1)
# return the counter value
return self.counter

42
Iterators, Generators, and Context Managers 43

We can then create an instance of the asynchronous iterator and traverse it using an async
for expression.
The complete example is listed below.
# SuperFastPython.com
# example of using an asynchronous iterator
import asyncio

# define an asynchronous iterator


class AsyncIterator():
# constructor, define some state
def __init__(self):
self.counter = 0

# create an instance of the iterator


def __aiter__(self):
return self

# return the next awaitable


async def __anext__(self):
# check for no further items
if self.counter >= 10:
raise StopAsyncIteration
# increment the counter
self.counter += 1
# simulate work
await asyncio.sleep(1)
# return the counter value
return self.counter

# main coroutine
async def main():
# loop over async iterator with async for loop
async for item in AsyncIterator():
print(item)

# execute the asyncio program


asyncio.run(main())

43
Iterators, Generators, and Context Managers 44

Q. What method is used to iterate one step of an asyn-


chronous iterator?
An asynchronous iterator can be stepped using the anext() built-in function that returns an
awaitable that executes one step of the iterator, e.g. one call to the __anext__() method.
This is an asynchronous version of the built-in next() method for stepping regular Python
iterators.

Q. What is an asynchronous generator?


A generator is a Python function that returns a value via a yield expression.
An asynchronous generator is a coroutine that uses the yield expression.
Unlike a function generator, the coroutine can schedule and await other coroutines and tasks.

Q. How is a coroutine function different from an asyn-


chronous generator?
A coroutine function does not have a yield expression, whereas an asynchronous generator
function has a yield expression.
Both coroutine functions and asynchronous generator functions can make use of await, async
for, and async with expressions.

Q. How would you write a simple asynchronous generator


and then iterate it?
We can define a simple asynchronous generator that traverses a list in a loop, awaiting a
sleep operation and yielding the iteration value each iteration.
For example:
# define an asynchronous generator
async def async_generator():
# normal loop
for i in range(10):
# block to simulate doing work
await asyncio.sleep(1)
# yield the result
yield i

The synchronous generator can be traversed using an async for expression.

44
Iterators, Generators, and Context Managers 45

The complete example is listed below.


# SuperFastPython.com
# example of using an asynchronous generator
import asyncio

# define an asynchronous generator


async def async_generator():
# normal loop
for i in range(10):
# block to simulate doing work
await asyncio.sleep(1)
# yield the result
yield i

# main coroutine
async def main():
# loop over async generator with async for loop
async for item in async_generator():
print(item)

# execute the asyncio program


asyncio.run(main())

Q. How would you traverse an asynchronous generator


in a list comprehension?
An asynchronous generator can be traversed in a list comprehension using an async for
expression.
For example:
...
# loop over async generator with async comprehension
results = [item async for item in async_generator()]

Q. What is an await comprehension and give an example?


The await expression may be used within a list, set, or dict comprehension referred to as
an await comprehension.
Like an async comprehension, it may only be used within an asyncio coroutine or task.
This allows a data structure, like a list, to be created by suspending and awaiting a series of
awaitables.

45
Iterators, Generators, and Context Managers 46

For example:
...
# await list comprehension
results = [await a for a in awaitables]

The current coroutine will be suspended to execute awaitables sequentially, which is different
and perhaps slower than executing them concurrently using asyncio.gather().

Q. What does async with do?


The async with expression is for creating and using asynchronous context managers.
It is an extension of the with expression for use in coroutines within asyncio programs.
The async with expression is just like the with expression used for context managers, except
it allows asynchronous context managers to be used within coroutines.
An asynchronous context manager may suspend on the enter and exit of the enclosed code
block.

Q. What two methods must exist on an asynchronous


context manager?
An asynchronous context manager must implement the __aenter__() and __aexit__()
methods.
The __aenter__() method is a coroutine that is called prior to executing the enclosed block.
The __aexit__() method is a coroutine that is always called after the enclosed block is
exited.

Q. How would you write a simple asynchronous context


manager and use it?
We can define a simple asynchronous context manager that reports a message and sleeps on
enter and exit.
For example:
# define an asynchronous context manager
class CustomContextManager:
# enter the async context manager
async def __aenter__(self):
# report a message
print('>entering the context manager')
# block for a moment

46
Iterators, Generators, and Context Managers 47

await asyncio.sleep(0.5)

# exit the async context manager


async def __aexit__(self, exc_type, exc, tb):
# report a message
print('>exiting the context manager')
# block for a moment
await asyncio.sleep(0.5)

We can use this context manager in an asyncio program,


The complete example is listed below.
# SuperFastPython.com
# example of using asynchronous context manager
import asyncio

# define an asynchronous context manager


class CustomContextManager:
# enter the async context manager
async def __aenter__(self):
# report a message
print('>entering the context manager')
# block for a moment
await asyncio.sleep(0.5)

# exit the async context manager


async def __aexit__(self, exc_type, exc, tb):
# report a message
print('>exiting the context manager')
# block for a moment
await asyncio.sleep(0.5)

# define a simple coroutine


async def custom_coroutine():
# report a message
print('before the context manager')
# create and use the asynchronous context manager
async with CustomContextManager() as manager:
# report the result
print(f'within the manager')
# report a message
print('after the context manager')

# start

47
Iterators, Generators, and Context Managers 48

asyncio.run(custom_coroutine())

Further Reading
This section provides additional resources on or related to this topic.
• PEP 492 – Coroutines with async and await syntax.
https://peps.python.org/pep-0492/
• PEP 3156 – Asynchronous IO Support Rebooted: the asyncio Module.
https://peps.python.org/pep-3156/
• PEP 525 – Asynchronous Generators.
https://peps.python.org/pep-0525/
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html

48
Synchronization Primitives

This section covers questions on synchronization primitives for coroutines.


Synchronization primitives allow coroutines to coordinate and synchronize their behavior,
allowing asyncio programs to avoid race conditions, deadlocks, and other concurrency failure
modes.
Let’s get started.

Q. What does coroutine-safe mean?


Thread-safe refers to program code that can be executed free of concurrency errors by multiple
threads concurrently.
Primarily, it refers to the fact that the code is free of race conditions.
A race condition is a bug in concurrency programming.
It is a failure case where the behavior of the program is dependent upon the order of execution
by two or more threads. This means the behavior of the program will not be predictable,
possibly changing each time it is run.
Coroutine-safe is the idea of thread-safe applied to coroutines.
Although two or more coroutines cannot execute at the same time within a thread, it is
possible for program state and resources to be corrupted or made inconsistent via concurrent
execution.

Q. Can we get race conditions with coroutines?


Yes.
It is possible to have race conditions with coroutines in asyncio.
The coroutine below will suffer race conditions if executed many times concurrently in an
asyncio program.
# task that operates on a shared variable
async def task():

49
Synchronization Primitives 50

# declare global variable


global value
# retrieve the value
tmp = value
# suspend for a moment
await asyncio.sleep(0)
# update the tmp value
tmp = tmp + 1
# suspend for a moment
await asyncio.sleep(0)
# store the updated value
value = tmp

Q. Can we get deadlocks with coroutines?


Yes.
It is possible to have deadlocks with coroutines in asyncio.
For example, if we have a program with two coroutines and they await each other, then a
deadlock will result and the program will not progress.
The example below demonstrates a deadlock of this type.
# SuperFastPython.com
# example of a coroutine deadlock
import asyncio

# takes a task and awaits it


async def task(other):
# report a message
print(f'awaiting the task: {other.get_name()}')
# wait for the task to finish
await other

# main coroutine
async def main():
# get the current task
task1 = asyncio.current_task()
# create and execute the first task
task2 = asyncio.create_task(task(task1))
# execute the task, which will await task2
await task(task2)

# entry point for the asyncio program

50
Synchronization Primitives 51

asyncio.run(main())

Q. How can we use a mutex to protect a critical section


from coroutines?
A mutual exclusion lock or mutex lock is a synchronization primitive intended to prevent a
race condition.
An instance of the lock must be created and shared among coroutines in order to protect a
resource or critical section.
For example:
...
# create a lock
lock = asyncio.Lock()

The lock can be acquired by calling the acquire() coroutine.


This is done at the beginning of a critical section.
This call must be awaited because it may block if the lock is currently being held by another
coroutine that is currently suspended.
This will suspend the calling coroutine and potentially allow the coroutine holding the lock
to progress,
For example:
...
# acquire the lock
await lock.acquire()

Once the critical section is completed, the lock can be released.


This is not a blocking call.
...
# release the lock
lock.release()

The lock must always be released.


This can be achieved using the asynchronous context manager interface and the async with
expression.
For example:
...
# acquire the lock
async with lock:
# critical section

51
Synchronization Primitives 52

# ...
# lock is released automatically...

Q. How can a coroutine check if a mutex is locked?


We can check if a coroutine is locked by calling the locked() method.
This method returns True if the lock is currently acquired by a coroutine and False otherwise.
For example:
...
# check if a lock is currently acquired
if lock.locked():
# ...

Q. When might we use an Event?


An asyncio.Event can be used when we want one coroutine to notify one or more other
coroutines.
For example, one or more coroutines may wait for an event to be set before progressing with
their own tasks.
For example:
...
# wait for the event to be set
await event.wait()

We can also use an asyncio.Event to stop tasks that are running concurrently.
For example, each task may check the status of the event each iteration of their task and if
the event is set, they can return, effectively stopping the task.
For example:
...
# check if the event is set
if event.is_set():
# stop processing
return

Q. How can we check if an Event is set?


We can check if an asyncio.Event has been set via the is_set() method which will return
True if the event is set, or False otherwise.
For example:

52
Synchronization Primitives 53

...
# check if the event is set
if event.is_set():
# do something...

Q. How can a coroutine set an Event?


A coroutine can set an asyncio.Event by calling the set() method.
Any coroutines waiting on the event to be set will be notified.
For example:
...
# set the event
event.set()

Q. What is an asyncio condition variable?


In concurrency, a condition variable (also called a monitor) allows multiple coroutines to be
notified about some result.
It combines both a mutual exclusion lock (mutex) and an event allowing exclusive access and
notification.
An event can be used to notify other coroutines, but it cannot be used to protect a critical
section and enforce mutual exclusion.
A condition variable can be acquired by a coroutine (like a mutex) after which it can wait to
be notified by another coroutine that something has changed like an event. While waiting,
the coroutine is blocked and releases the lock for other coroutines to acquire.
Another coroutine can then acquire the condition variable, make a change, and notify one,
all, or a subset of coroutines waiting on the condition variable that something has changed.
The waiting coroutine can then wake up (be scheduled by the operating system), re-acquire
the condition variable, perform checks on any changed state, and perform required actions.

Q. How can we wait to be notified with an asyncio


condition variable?
In order for a coroutine to make use of a asyncio.Condition object, it must acquire it and
release it, like a mutex lock.
This can be achieved manually with the acquire() and release() methods.
Acquiring the condition via acquire() returns a coroutine object that requires that the caller
use an await expression.

53
Synchronization Primitives 54

For example, we can acquire the condition variable, do something, then release the condition
as follows:
...
# acquire the condition
await condition.acquire()
# do something
# ...
# release the condition
condition.release()

An alternative to calling the acquire() and release() methods directly is to use the async
context manager, which will perform the acquire/release automatically for us, for example:
...
# acquire the condition
async with condition:
# do something
# ...

Once the condition is acquired, we can wait on it.


This will suspend the calling coroutine until another coroutine notices it via the condition
with the notify() function (seen later).
This can be achieved via the wait() method that will return a coroutine and must be awaited.
For example:
...
# acquire the condition
async with condition:
# wait to be notified
await condition.wait()

Q. How can we notify all coroutines waiting on a condi-


tion?
We can notify all coroutines waiting on an asyncio.Condition by acquiring the condition
and then calling the notify_all() method.
For example:
...
# acquire the condition
async with condition:
# notify all coroutines waiting on the condition
condition.notify_all()

54
Synchronization Primitives 55

Q. What is a semaphore?
A semaphore is a concurrency primitive that allows a limit on the number of coroutines that
can acquire a lock protecting a critical section.
It is an extension of a mutual exclusion (mutex) lock that adds a count for the number of
coroutines that can acquire the lock before additional coroutines will block. Once full, new
coroutines can only acquire a position on the semaphore once an existing coroutine holding
the semaphore releases a position.
Internally, the semaphore maintains a counter protected by a mutex lock that is decremented
each time the semaphore is acquired and incremented each time it is released.
When a semaphore is created, the upper limit on the counter is set. If it is set to 1, then the
semaphore will operate like a mutex lock.

Q. How can we configure a semaphore?


We can configure an asyncio.Semaphore by specifying the initial counter value as an
argument to the class constructor.
For example, we might want to set it to 100:
...
# create a semaphore with a limit of 100
semaphore = asyncio.Semaphore(100)

Q. How can we use a semaphore to limit access to a


critical section?
We can limit access to a critical section using an asyncio.Semaphore by requiring the
semaphore to be acquired in order to execute the critical section.
This can be achieved by calling and awaiting the acquire() method beforehand and the
release() method afterward.
For example:
...
# acquire the semaphore
await semaphore.acquire()
# critical section
# ...
# release the semaphore
semaphore.release()

This can also be achieved using the asynchronous context manager interface and the async
with expression.

55
Synchronization Primitives 56

For example:
...
# acquire the semaphore
async with semaphore:
# critical section
# ...

Q. How can we use a semaphore like a mutex?


We can use an asyncio.Semaphore like a mutex by configuring the initial counter value to
be one.
For example:
...
# create a semaphore that acts like a mutex
semaphore = asyncio.Semaphore(1)

Using this semaphore will only allow a single coroutine access to a critical section at a time,
just like a mutex lock.

Q. What happens if a coroutine tries to acquire a


semaphore that has no space?
If a coroutine attempts to acquire a coroutine with no space (e.g. the counter is zero), then
the caller will be suspended until space becomes available.
This will happen automatically when awaiting the acquire() method directly or indirectly
via the asynchronous context manager interface on the semaphore.

Further Reading
This section provides additional resources on or related to this topic.
• Thread safety, Wikipedia.
https://en.wikipedia.org/wiki/Thread_safety
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Asyncio Synchronization Primitives.
https://docs.python.org/3/library/asyncio-sync.html

56
Queues

This section covers questions on queues for coroutines.


Queues are an important and fundamental data structure used in asyncio programs.
Let’s get started.

Q. Why would we use queues in an asyncio program?


Queues can be used in asyncio programs to share Python objects safely between coroutines.
Python objects can be shared in a coroutine-safe manner without fear or race conditions.
Additionally, objects can be added to and retrieved from queues in coroutines without blocking
the event queue, e,g. using the await expression.

Q. What 3 types of queues does asyncio provide?


The three types of queues provided for use in asyncio are:
• asyncio.Queue
• asyncio.LifoQueue
• asyncio.PriorityQueue

Q. How can we define a fixed-sized queue?


We can define a fixed-sized queue by specifying the maxsize argument in the class constructor
when creating a queue.
For example:
...
# create a fixed-sized queue
queue = asyncio.Queue(maxsize=100)

57
Queues 58

Q. How do we add and remove items from a queue?


We can add a Python object to a queue via the put() method.
It is a coroutine method and must be awaited. If the queue has a limited capacity, the call
will be suspended until space becomes available on the queue.
For example:
...
# put an item on the queue
await queue.put(item)

We can put an item on the queue without waiting via the put_nowait() method.
We can remove an item from the queue using the get() method.
This is a coroutine method and must be awaited. If the queue does not have any items
available, the caller will be suspended until an item becomes available.
For example:
...
# get an item from the queue
item = await queue.get()

We can retrieve an item without waiting via the get_nowait() method.

Q. What happens if a coroutine adds an item to a queue


that is full?
If a coroutine adds an item to a queue that has a limited capacity and is full, then the calling
coroutine will suspend until space becomes available in the queue to add the item.

Q. Why would we use the task_done() and join() meth-


ods?
We would use the task_done() and join() methods on the queue when a coroutine needs
to be notified when all items placed on the queue have been processed.
A coroutine consuming items from the queue can mark each item as processed via a call to
the task_done() method.
For example:
...
# mark an item as processed
queue.task_done()

58
Queues 59

A coroutine can wait for all items on the queue to be processed (at the time of the call) via
the join() method, which must be awaited.
This may be done by a coroutine that puts items on the queue and needs to know when all
those items have been consumed and handled by other coroutines.
For example:
...
# wait for all items to be processed
await queue.join()

Further Reading
This section provides additional resources on or related to this topic.
• Queue (abstract data type), Wikipedia.
https://en.wikipedia.org/wiki/Queue_(abstract_data_type)
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html
• Asyncio Queues.
https://docs.python.org/3/library/asyncio-queue.html

59
Subprocesses

This section covers questions on subprocesses used in asyncio programs.


Subprocesses allow system commands to be executed by subprocesses and for asyncio programs
to interact with those programs using non-blocking reads and writes.
Let’s get started.

Q. How is running a command directly vs via the shell


different?
Running a command in asyncio via the create_subprocess_exec() function will execute
the command directly.
Running a command via the shell in asyncio via the create_subprocess_shell() function
will execute the command indirectly, via the shell interpreter.
Unlike executing the command directly, executing a command via the shell allows the
capabilities of the shell to be used when executing the command, such as access to shell
variables and the use of wildcards.

Q. What are some common shells on Unix-based oper-


ating systems?
Some common shells on Unix-based operating systems include sh, bash, and zsh.

Q. How can we run a command directly from asyncio?


We can execute a system command directly from asyncio via the create_subprocess_exec()
function.
This function takes the command to execute and any arguments to the command and
must be awaited. It will return once the command has been started and will provide an
asyncio.subprocess.Process instance for interacting with the command.
For example:

60
Subprocesses 61

...
# execute a command directly
process = await create_subprocess_exec('ls', '-l')

Q. How can we run a command using the shell from


asyncio?
We can execute a system command via the shell using the create_subprocess_shell()
function.
This function takes the command to execute and any arguments to the command and
must be awaited. It will return once the command has been started and will provide an
asyncio.subprocess.Process instance for interacting with the command.
For example:
...
# execute a command via the shell
process = await create_subprocess_shell('ls -l')

Q. How do we interact with the subprocess running the


command?
We can interact with a subprocess running a system command from an asyncio program via
the asyncio.subprocess.Process instance returned when starting the command.
The Process object can be used to read data from or write data to the subprocess. It can
also be used to wait for the subprocess to terminate and to terminate the subprocess directly.

Q. Does the subprocess continue running if the asyncio


event loop exits?
Yes.
If the asyncio event loop exits while a system command is running in a subprocess, then the
subprocess will continue to execute and may prevent the main thread of the program from
terminating.

Q. How can we read data from a subprocess?


We can read byte data from a subprocess via the communicate() method.
For example:

61
Subprocesses 62

...
# read byte data from the subprocess
byte_data = await process.communicate()

Q. How can we write data to a subprocess?


We can write byte data to a subprocess via the communicate() method and specify the
input argument.
For example:
...
# write byte data to the subprocess
await process.communicate(input=byte_data)

Q. How can we stop a command running in a subprocess?


We can stop a command running in a subprocess in an asyncio program by calling the
terminate() or kill() method on the asyncio.subprocess.Process object.
The terminate() method will raise a SIGTERM signal in the subprocess, whereas the kill()
method will raise a SIGKILL signal in the subprocess.
For example:
...
# stop the command running in a subprocess
process.terminate()

Q. How can we wait for a subprocess to finish?


We can wait for a subprocess to finish in an asyncio program by calling the wait() method.
This is a coroutine method and must be awaited.
For example:
...
# wait for the subprocess to finish
await process.wait()

Further Reading
This section provides additional resources on or related to this topic.
• asyncio – Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html

62
Subprocesses 63

• Asyncio Subprocesses.
https://docs.python.org/3/library/asyncio-subprocess.html

63
Streams

This section covers questions on streams used in asyncio programs.


Streams are socket connections to other systems, allowing asyncio programs to interact with
those systems using non-blocking reads and writes. Using non-blocking I/O streams may be
the central use case for asyncio.
Let’s get started.

Q. What are 5 common application-layer protocols used


on the internet?
Five examples of ASCII-based protocols used on the internet are:
1. Hyper Text Transfer Protocol (HTTP)
2. Simple Mail Transfer Protocol (SMTP)
3. File Transfer Protocol (FTP)
4. Domain Name Service (DNS)
5. Post Office Protocol (POP)

Q. What are 3 socket programs you could develop in-


volving a web server?
Three non-blocking socket programs that could be developed involving a web server include:
1. Web page scraping program.
2. Web page link testing and validation program.
3. Web page crawling program.

Q. How can we open a TCP client connection?


We can open a TCP client connection in an asyncio program using the open_connection()
function.
The function takes at least a host and port number, as well as many other arguments.

64
Streams 65

This is a coroutine function that must be awaited and returns a tuple containing a
StreamReader and StreamWriter for reading and writing from and to the connection.
For example:
...
# open a tcp socket connection
reader, writer = open_connection('www.google.com', 80)

Q. How can we open a TCP server connection?


We can open a TCP server connection using the start_server() function.
This is a coroutine function that must be awaited.
A coroutine callback function must be specified that will be called each time a connection
is made to the server. The callback function must take the tuple of StreamReader and
StreamWriter arguments.
The host and port number should also be specified as arguments to the start_server()
function.
For example:
# handle client connections
async def client_handler(reader, writer):
# ...

...
# start a tcp server
start_server(client_handler, '127.0.0.1', 7890)

Q. How would we connect to a web server on port 80?


We would connect to a web server on port 80 by calling the open_connection() function
and specify the hostname and port number 80.
For example:
...
# open a http client socket connection
reader, writer = open_connection('hostname', 80)

65
Streams 66

Q. How would we connect to a web server running


HTTPS on the regular port?
We can connect to a web server running HTTPS by calling the open_connection() function
and specify the hostname, port number 443, and set the ssl argument to True.
For example
...
# open an https client socket connection
reader, writer = open_connection('hostname', 443,
ssl=True)

Q. What are two ways to read lines from a StreamReader?


We can read lines of data from the StreamReader by repeatedly calling the readline()
function, or by using the reader with an async for expression.
For example, we can read lines of byte data one at a time via the readline() function as
follows:
...
# read lines of byte data
while True:
# read one line of byte data
line_bytes = reader.readline()
# ...

Alternatively, we can read lines of byte data one at a time using the async for expression
on the StreamReader directly.
For example:
...
# read lines of byte data
async for line_bytes in reader:
# ...

Q. How can we write lines of data to a StreamWriter?


We can write lines of data using the StreamWriter by calling the writelines() method and
providing an iterable of lines in byte data.
For example:
...
# write lines of byte data
writer.writelines(byte_data_lines)

66
Streams 67

Alternatively, we can enumerate strings, encode each as byte data and call the write()
method for each.
For example:
...
# write string lines
for line in lines:
# encode as byte data
line_bytes = line.encode()
# write line byte data
writer.write(line_bytes)

Q. What is the drain() method and when would we use


it?
The drain() method will suspend the caller until all byte data has been transmitted to the
server and the socket connection is available to be used again.
It is a coroutine method and must be awaited.
For example:
...
# wait for the socket connection to be available
await writer.drain()

Q. How do we close a connection?


We can close an open client connection by calling the close() method on the associated
StreamWriter.
For example:
...
# close the connection
writer.close()

Further Reading
This section provides additional resources on or related to this topic.
• Computer network programming, Wikipedia.
https://en.wikipedia.org/wiki/Computer_network_programming
• Application layer, Wikipedia.
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

67
Streams 68

• asyncio – Asynchronous I/O.


https://docs.python.org/3/library/asyncio.html
• Asyncio Streams.
https://docs.python.org/3/library/asyncio-stream.html

68
Conclusions

Well Done!
Congratulations, you made it to the end of the book.
You have challenged your self with pointed interview questions.
You know how to use Python asyncio and bring coroutine-based concurrency and asynchronous
programming to your project.
Thank you for letting me help you on your journey into Python concurrency.
Jason Brownlee, Ph.D.
SuperFastPython.com
2022.

Resources For Diving Deeper


This section lists some useful additional resources for further reading.

APIs
• Concurrent Execution API - Python Standard Library.
https://docs.python.org/3/library/concurrency.html
• multiprocessing API - Process-based parallelism.
https://docs.python.org/3/library/multiprocessing.html
• threading API - Thread-based parallelism.
https://docs.python.org/3/library/threading.html
• concurrent.futures API - Launching parallel tasks.
https://docs.python.org/3/library/concurrent.futures.html
• asyncio API - Asynchronous I/O.
https://docs.python.org/3/library/asyncio.html

Books
• High Performance Python, Ian Ozsvald, et al., 2020.
https://amzn.to/3wRD5MX

69
Conclusions 70

• Using AsyncIO in Python, Caleb Hattingh, 2020.


https://amzn.to/3lNp2ml
• Python Concurrency with asyncio, Matt Fowler, 2022.
https://amzn.to/3LZvxNn
• Effective Python, Brett Slatkin, 2019.
https://amzn.to/3GpopJ1
• Python Cookbook, David Beazley, et al., 2013.
https://amzn.to/3MSFzBv
• Python in a Nutshell, Alex Martelli, et al., 2017.
https://amzn.to/3m7SLGD

Getting More Help


Do you have any questions?
Below provides some great places online where you can ask questions about Python program-
ming and Python concurrency:
• Stack Overview.
https://stackoverflow.com/
• Python Subreddit.
https://www.reddit.com/r/python
• LinkedIn Python Developers Community.
https://www.linkedin.com/groups/25827
• Quora Python (programming language).
https://www.quora.com/topic/Python-programming-language-1

Contact the Author


You are not alone.
If you ever have any questions about Python concurrency, please contact me directly:
• Super Fast Python - Contact Page
https://SuperFastPython.com/contact/
I will do my best to help.

70
About the Author

Jason Brownlee, Ph.D. helps Python developers bring modern concurrency methods to their
projects with hands-on tutorials. Learn more at SuperFastPython.com.
Jason is a software engineer and research scientist with a background in artificial intelligence
and high-performance computing. He has authored more than 20 technical books on machine
learning and has built, operated, and exited online businesses.

Figure 1: Photo of Jason Brownlee

71
Interview Questions Series

Prepare for a technical interview or test your skills.


Python Threading Interview Questions. Answers to 120+ interview questions on Python
threading.
https://SuperFastPython.com/ptiq
Python Multiprocessing Interview Questions. Answers to 180+ interview questions on
Python multiprocessing.
https://SuperFastPython.com/pmiq
Python Concurrent Futures Interview Questions. Answers to 130+ interview questions
on Python ThreadPoolExecutor and ProcessPoolExecutor.
https://SuperFastPython.com/pcfiq
Python Asyncio Interview Questions. Answers To 150+ Interview Questions On
Asynchronous Programming and Coroutines in Python.
https://SuperFastPython.com/paiq

72

You might also like