Hi Florian,

I'm sorry this wasn't documented in the official docs. (It's described in
PEP 3156 in the two sections on  Futures and Tasks.)

The behavior is indeed as intended, and came from a long discussion and
deep thinking about cancellation. When a Task (as opposed to a Future) is
cancelled, the coroutine that the Task wraps will receive a
asyncio.CancelledError, which will be handled by any except/finally clauses
active at that point. But this will only run when the event loop goes
through its next cycle. It is possible (though not recommended) for a
coroutine to completely ignore this exception, and it is also possible for
it to yield, waiting for additional blocking events, before finally
exiting. The cancelled() flag is only set once the coroutine has completed.

We should really document this -- do you have a suggestion for a sentence
or paragraph that we should add to the docs?

As a practical matter, you shouldn't write code that depends on checking
whether a task has a pending cancellation or not (since it may not take
effect at all). In your particular app, if you really need to keep track of
which tasks you have already tried to cancel, I recommend a separate
variable containing a set of tasks which you have cancelled for that
purpose.

Finally, your description of how you are using this sounds like you are
really thinking of your collection of tasks as a collection of callbacks
that you are managing manually. Things might (or might not) become easier
if you let go of this perspective -- e.g. instead of looping over all tasks
and cancelling slow ones, wrap each task in a wait_for() call that enforces
a timeout.



On Fri, Mar 28, 2014 at 2:22 PM, Florian Rüchel
<[email protected]>wrote:

> I had an issue with my code where I called Task.cancel on an instance and
> it would stay in state pending (cancelled returning False). I did some
> digging and found in the source that Task overwrites the cancel method and
> does not directly set the new state. Going back to the documentation, I did
> not find anything that documents this behavior.
>
> The problematic use case was a situation like the following: I iterate
> over a list of tasks, check various properties (like how long they are
> running etc.) and based on that decide to either give it more time or
> cancel it or check its results if its done. After having handled all
> running tasks, I do other work and later return to check them again. When I
> checked again, I explicitly checked the case where I already had cancelled
> a task. In this next iteration I want to see if it is done yet or if it
> needs some more time cleaning up. However, to my surprise, task.cancelled()
> returned false, which led me to execute task.cancel() again, which in turn
> raised a CancellationError inside the finally-block doing cleanup. I then
> added something like this:
>
> task.cancel()
> assert task.canelled()
>
> A statement that, on a future, has to succeed (as I see it from the
> source) but on a task must fail. Is there a reason this behavior is not
> documented or did I miss it somewhere?
>
> Also since this seems to be desired behavior, is there a reliable way to
> perform this check on a task? In __repr__ you check against _must_cancel
> for the difference between displaying "PENDING" and "CANCELLING" (this
> actually led me to the point of looking at the Task source). However, this
> is an internal variable and so I probably shouldn't use it. The only other
> thing I can come up with is using the return value and store it somewhere
> else and then check that variable, however that seems rather "dirty",
> storing a value that should belong to the task outside of it.
>



-- 
--Guido van Rossum (python.org/~guido)

Reply via email to