tasks.py 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """Minimal backported implementation of asyncio._PyTask from 3.11 compatible down to Python 3.8."""
  2. import asyncio.tasks
  3. import sys
  4. from asyncio import AbstractEventLoop
  5. from typing import Coroutine, TypeVar, Any, Optional
  6. _T = TypeVar("_T")
  7. # See https://github.com/python/cpython/blob/3.11/Lib/asyncio/tasks.py
  8. class Task(asyncio.tasks._PyTask): # type: ignore[name-defined, misc]
  9. """A coroutine wrapped in a Future."""
  10. def __init__(
  11. self,
  12. coro: Coroutine[Any, Any, _T],
  13. *,
  14. loop: Optional[AbstractEventLoop] = None,
  15. name: Optional[str] = None,
  16. ) -> None:
  17. self._num_cancels_requested = 0
  18. # https://github.com/python/cpython/blob/3.11/Modules/_asynciomodule.c#L2026
  19. # Backport Note: self._context is temporarily patched in Runner.run() instead.
  20. super().__init__(coro, loop=loop, name=name)
  21. def cancel(self, msg: Optional[str] = None) -> bool:
  22. """Request that this task cancel itself.
  23. This arranges for a CancelledError to be thrown into the
  24. wrapped coroutine on the next cycle through the event loop.
  25. The coroutine then has a chance to clean up or even deny
  26. the request using try/except/finally.
  27. Unlike Future.cancel, this does not guarantee that the
  28. task will be cancelled: the exception might be caught and
  29. acted upon, delaying cancellation of the task or preventing
  30. cancellation completely. The task may also return a value or
  31. raise a different exception.
  32. Immediately after this method is called, Task.cancelled() will
  33. not return True (unless the task was already cancelled). A
  34. task will be marked as cancelled when the wrapped coroutine
  35. terminates with a CancelledError exception (even if cancel()
  36. was not called).
  37. This also increases the task's count of cancellation requests.
  38. """
  39. self._log_traceback = False
  40. if self.done():
  41. return False
  42. self._num_cancels_requested += 1
  43. # These two lines are controversial. See discussion starting at
  44. # https://github.com/python/cpython/pull/31394#issuecomment-1053545331
  45. # Also remember that this is duplicated in _asynciomodule.c.
  46. # if self._num_cancels_requested > 1:
  47. # return False
  48. if self._fut_waiter is not None:
  49. if sys.version_info >= (3, 9):
  50. if self._fut_waiter.cancel(msg=msg):
  51. # Leave self._fut_waiter; it may be a Task that
  52. # catches and ignores the cancellation so we may have
  53. # to cancel it again later.
  54. return True
  55. else:
  56. if self._fut_waiter.cancel():
  57. return True
  58. # It must be the case that self.__step is already scheduled.
  59. self._must_cancel = True
  60. if sys.version_info >= (3, 9):
  61. self._cancel_message = msg
  62. return True
  63. def cancelling(self) -> int:
  64. """Return the count of the task's cancellation requests.
  65. This count is incremented when .cancel() is called
  66. and may be decremented using .uncancel().
  67. """
  68. return self._num_cancels_requested
  69. def uncancel(self) -> int:
  70. """Decrement the task's count of cancellation requests.
  71. This should be called by the party that called `cancel()` on the task
  72. beforehand.
  73. Returns the remaining number of cancellation requests.
  74. """
  75. if self._num_cancels_requested > 0:
  76. self._num_cancels_requested -= 1
  77. return self._num_cancels_requested