One common patter when using Python’s async library and the co-routines is to create long-lived tasks which process elements of an async queue as they come in. Normally these tasks will have an infinite loop with await q.get() waiting for the next element to process. After the task which inserts new elements into the queue completes it is necessary to cancel the processing tasks. This is how to do it:

# This task generates items for processing
tgen = async.create_task(genitems())

# These are the processing tasks
tproc1 = async.create_task(proctask())
tproc2 = async.create_task(proctask())

# Wait for the generation task to finish
await async.gather(tgen)
# Now need to cancel the processing tasks

# Now await the cancelled tasks so that the cancellation message is processed
await async.gather(tproc1, tproc2, return_exceptions=True)

What the code above illustrates is that after the cancel() method has been called it is necessary to give that task a chance to actually process this cancellation. The cancelled tasks will raise exceptions in them; calling gather with return_exceptions=True avoids those exceptions being thrown in the parent task.