3

How to change the behavior of a task cancellation from where the task is being cancelled?

What I would dream of:

task = ensure_future(foo()) def foo_done(task) try: return task.get_result() except CancelError as e: when, why = e.args if when == "now" # do something... elif when == "asap": # do something else... else: # do default print(f"task cancelled because {why}") task.add_done_callback(foo_done) [...] task.cancel("now", "This is an order!") 

I could attach an object to the task before calling task.cancel(), and inspect it later.

task = ensure_future(foo()) def foo_done(task) try: return task.get_result() except CancelError as e: when = getattr(task, "_when", "") why = getattr(task, "_why", "") if when == "now" # do something... elif when == "asap": # do something else... else: # do default print(f"task cancelled because {why}") task.add_done_callback(foo_done) [...] task._when = "now" task._why = "This is an order!" task.cancel() 

But it looks clunky in some situation, when I want to capture the CancelError within the task being process for example:

async def foo(): # some stuff try: # some other stuff except CancellError as e: # here I have easily access to the error, but not the task :( [...] 

I'm looking for a more Pythonic way to do it.

2
  • how "now" is different from "asap" taking into account that foo_done is a synchronous function. Commented Feb 15, 2019 at 6:59
  • I realize that "now" and "asap" was poor examples of I wanted. what i meant is give to the task the reason of the cancellation, and execute some code according to it: if reason = A do this, if reason = B, do that, etc. Commented Feb 15, 2019 at 23:26

1 Answer 1

2

Your solution to decorate the Task with data relevant for your exception is in fact a good one. Within the task you can access the task being processed with asyncio.Task.current_task().

You could also achieve the syntax you dream of using the following decorator (untested):

def propagate_when(fn): async def wrapped(*args, **kwds): try: return await fn(*args, **kwds) except CancelledError as e: e.when = getattr(asyncio.Task.current_task(), '_when', None) raise return wrapped 

Decorating a coroutine with @propagate_when allows the code in foo_done to access e.when when handling CancelledError. The downside is that e.when will not be available inside the task - there you'd still have to use current_task(). Because of that inconsistency I would recommend sticking to reading from the task object.

Several related recommendations:

  • Put the cancellation code in a utility function that stores the object you pass it and then calls task.cancel(). This thin layer of encapsulation should remove the "clunky" feel from the current code.

  • Use prefixed attribute names - short and generic ones like _when can cause a clash in a future release. (I understand it was just an example, but unprefixed names are always in danger of clashing.)

  • Decorate the task with a single object, putting actual data in its attributes. It makes the retrieval simpler and cleaner, and the gives you the option of implementing methods on the stored object.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks! what i was looking for was indeed asyncio.Task.current_task()

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.