Skip to content

Commit

Permalink
bpo46771 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Jul 9, 2022
1 parent 7a34172 commit 63221c9
Showing 1 changed file with 67 additions and 0 deletions.
67 changes: 67 additions & 0 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,23 @@ Creating Tasks
Added the *context* parameter.


Task Cancellation
=================

Tasks can easily and safely be cancelled.
When a task is cancelled, asyncio will raise a :exc:`asyncio.CancelledError`
into the task at the next opportunity.

Coroutines should only catch :exc:`asyncio.CancelledError` if they
need to perform clean-up logic when they are cancelled, and even in
that case the :exc:`asyncio.CancelledError` should be propagated when
clean-up is complete. Most code can safely ignore `CancelledError`.

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

Should this mention that the best way to do cancel-safe cleanup is using try/finally? (Is it the safest way? What if the coroutine is never run till completion?)

This comment has been minimized.

Copy link
@Tinche

Tinche Jul 10, 2022

Author Owner

Hm, good point about try/finally. I guess it depends. If I was implementing a connection pool for an HTTP client or a database library, I would handle the happy path and the cancellation path differently, so I wouldn't use try/finally, I might use try/except/else instead. Happy path -> return the connection to the pool, cancellation path -> most likely give up on the connection, remove it from the pool and attempt to gracefully close it in the background.

How is it possible for a task to never run again? If the event loop gets stopped? I feel like in this situation there's no robust way to perform resource cleanup in any case.

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

I think the scenario is only possible if the event loop is suddenly stopped and never resumed. But this would be a problem using try/except/else too. There's also something with async generators that sys.set_asyncgen_hooks is supposed to help with (but which I don't fully understand). In the end I think it's a red herring and we might as well recommend try/finally. Even if you want to do something different for success/failure, I feel it's better to set a flag at the end of the try block that you check in the finally block rather than explicitly catching CancelledError, since there are other exceptions that could happen. But I don't think we should bring that up. Just mentioning that try/finally will satisfy the requirement would be sufficient.

Anyway, are you going to turn this into a PR?

This comment has been minimized.

Copy link
@Tinche

Tinche Jul 11, 2022

Author Owner

Yes sir, here's the PR with all your feedback applied: python#94764


Important asyncio components, like :class:`asyncio.TaskGroup` and the
:func:`asyncio.timeout` context manager, are implemented using cancellation
internally.


Task Groups
===========

Expand Down Expand Up @@ -537,6 +554,56 @@ Shielding From Cancellation
Timeouts
========

A convenient way to limit the amount of time spent waiting on
something is to use the :func:`asyncio.timeout` and
:func:`asyncio.timeout_at`

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

I wouldn't mention timeout_at this early in the introduction, it's a bit of an esoteric use case and people will understand how it fits in even if you don't mention it explicitly here.

:ref:`asynchronous context manager <async-context-managers>`.

Example::

async def main():
async with asyncio.timeout(10):
await long_running_task()

If ``long_running_task`` takes more than 10 seconds to complete,
the context manager will cancel the current task and handle
the resulting :exc:`asyncio.CancelledError` internally, transforming it
into a :exc:`asyncio.TimeoutError`, which can be caught and handled.

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

Clarify that the transformation happens after CancelledError has bubbled out of the task, and TimeoutError can only be caught outside the context manager.

IOW, this doesn't work:

async with asyncio.timeout(10):
    try:
        await long_running_task()
    except asyncio.TimeoutError:
        <doesn't catch the 10 seconds timeout, though it may catch shorter timeouts inside l_r_t()>

Example::

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

"Example of catching asyncio.TimeoutError:"


async def main():
try:
async with asyncio.timeout_at(deadline):
await long_running_task()
except TimeoutError:
print("The long operation timed out, but we've handled it.")

print("This statement will run regardless.")

The context manager produced by :func:`asyncio.timeout` can be
rescheduled to a different deadline and inspected.

Example::

async def main():
try:
async with asyncio.timeout(10) as cm:
# We changed our mind, giving it more time.
new_deadline = cm.when + 10
cm.reschedule(new_deadline)

await long_running_task()
except TimeoutError:
pass

if cm.expired:
print("Looks like we haven't finished on time.")

The timeout context managers can be safely nested.

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 10, 2022

s/he t//


This comment has been minimized.

Copy link
@Hnasar

Hnasar Jul 13, 2022

How do timeout context managers interact with TaskGroups? Is it like trio?

with trio.move_on_after(TIMEOUT):
    async with trio.open_nursery() as nursery:
        nursery.start_soon(child1)
        nursery.start_soon(child2)

An example or explanation would be nice.

This comment has been minimized.

Copy link
@Tinche

Tinche Jul 13, 2022

Author Owner

I'd say they are independent concepts, and can be freely combined.

This comment has been minimized.

Copy link
@gvanrossum

gvanrossum Jul 13, 2022

I think it makes sense to have an explicit example in the docs.

.. versionadded:: 3.7

.. coroutinefunction:: wait_for(aw, timeout)

Wait for the *aw* :ref:`awaitable <asyncio-awaitables>`
Expand Down

0 comments on commit 63221c9

Please sign in to comment.