Advanced

A limit on a wait

A wait with no limit turns a stalled task into a hung program. A timeout makes the stall a value, and the value decides what the run does next.

What you will see

3

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Check what a limit produces.

1

Late reported.

import asyncio async def main(): try: await asyncio.wait_for(asyncio.sleep(1), 0.1) except TimeoutError: print("late") asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
late
2

Groups split.

import asyncio async def one(n): await asyncio.sleep(n) return n async def main(): tasks = [asyncio.ensure_future(one(1)), asyncio.ensure_future(one(2))] done, pending = await asyncio.wait(tasks, timeout=0.1) print(len(done), len(pending)) asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
0 2
3

Stopped task.

import asyncio async def main(): t = asyncio.create_task(asyncio.sleep(1)) t.cancel() try: await t except asyncio.CancelledError: print("stopped") asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
stopped
4

Attempts capped.

attempts = 0 while attempts < 3: attempts = attempts + 1 print(attempts)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
3

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A wait gets a limit, the timeout fires, the still open work is cancelled, and the run reports what actually arrived.
1 set a limit on the wait
↓
2 the timeout fires
↓
3 cancel the open work
↓
4 report what arrived
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

import asyncio sets nothing on screen
async def main(): sets nothing on screen
try: sets nothing on screen
await asyncio.wait_for(asyncio.sleep(1), 0.1) sets nothing on screen
except TimeoutError: sets nothing on screen
print("late") late
asyncio.run(main()) sets nothing on screen
Why this works
The limit is on the wait

A timeout does not stop the work, it stops the waiting. Cancel the task or the resource stays held after the timeout.

Partial is not whole

A batch that returned two of three is not a smaller answer. Report the count that arrived and let the caller decide.

Examples

Wait with a limit

import asyncio

async def main():
    try:
        await asyncio.wait_for(asyncio.sleep(1), 0.1)
    except TimeoutError:
        print("late")

asyncio.run(main())

The limit is on the wait, not on the work. The task may still be running when the timeout fires.

A batch with a limit

import asyncio

async def one(n):
    await asyncio.sleep(n)
    return n

async def main():
    done, pending = await asyncio.wait(
        [asyncio.ensure_future(one(1)), asyncio.ensure_future(one(2))], timeout=0.1)
    print(len(done), len(pending))

asyncio.run(main())

A batch wait returns two groups. The pending group is the work that is still open.

Cancel what is left

import asyncio

async def main():
    task = asyncio.create_task(asyncio.sleep(1))
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("stopped")

asyncio.run(main())

A pending task keeps running until it is cancelled. Cancelling is what frees the resource.

A whole run capped

import asyncio

async def main():
    await asyncio.sleep(0)

asyncio.run(main())
print("finished")

A cap on the whole run is the blunt version. It is the right first move when no single wait owns the delay.

A retry with a cap

attempts = 0
while attempts < 3:
    attempts = attempts + 1
print(attempts)

A retry loop needs its own cap. A timeout per attempt plus a cap on attempts is what bounds the total.

Report the timeout

import sys
print("2 of 3 arrived", file=sys.stderr)

Say which part timed out. A silent partial result is the failure that makes a wrong number look correct.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Two locks, two orders
Previous Lesson
Two locks, two orders
Next Lesson
Reading a stalled run
Reading a stalled run →