Skip to content

Commit 44d1a59

Browse files
authored
bpo-32250: Implement asyncio.current_task() and asyncio.all_tasks() (#4799)
1 parent 9508402 commit 44d1a59

File tree

6 files changed

+696
-148
lines changed

6 files changed

+696
-148
lines changed

Doc/library/asyncio-task.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,28 @@ Task functions
528528
the event loop object used by the underlying task or coroutine. If it's
529529
not provided, the default event loop is used.
530530

531+
532+
.. function:: current_task(loop=None):
533+
534+
Return the current running :class:`Task` instance or ``None``, if
535+
no task is running.
536+
537+
If *loop* is ``None`` :func:`get_running_loop` is used to get
538+
the current loop.
539+
540+
.. versionadded:: 3.7
541+
542+
543+
.. function:: all_tasks(loop=None):
544+
545+
Return a set of :class:`Task` objects created for the loop.
546+
547+
If *loop* is ``None`` :func:`get_event_loop` is used for getting
548+
current loop.
549+
550+
.. versionadded:: 3.7
551+
552+
531553
.. function:: as_completed(fs, \*, loop=None, timeout=None)
532554

533555
Return an iterator whose values, when waited for, are :class:`Future`

Lib/asyncio/tasks.py

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED',
66
'wait', 'wait_for', 'as_completed', 'sleep',
77
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
8+
'current_task', 'all_tasks',
9+
'_register_task', '_unregister_task', '_enter_task', '_leave_task',
810
)
911

1012
import concurrent.futures
@@ -21,6 +23,20 @@
2123
from .coroutines import coroutine
2224

2325

26+
def current_task(loop=None):
27+
"""Return a currently executed task."""
28+
if loop is None:
29+
loop = events.get_running_loop()
30+
return _current_tasks.get(loop)
31+
32+
33+
def all_tasks(loop=None):
34+
"""Return a set of all tasks for the loop."""
35+
if loop is None:
36+
loop = events.get_event_loop()
37+
return {t for t, l in _all_tasks.items() if l is loop}
38+
39+
2440
class Task(futures.Future):
2541
"""A coroutine wrapped in a Future."""
2642

@@ -33,13 +49,6 @@ class Task(futures.Future):
3349
# _wakeup(). When _fut_waiter is not None, one of its callbacks
3450
# must be _wakeup().
3551

36-
# Weak set containing all tasks alive.
37-
_all_tasks = weakref.WeakSet()
38-
39-
# Dictionary containing tasks that are currently active in
40-
# all running event loops. {EventLoop: Task}
41-
_current_tasks = {}
42-
4352
# If False, don't log a message if the task is destroyed whereas its
4453
# status is still pending
4554
_log_destroy_pending = True
@@ -52,19 +61,25 @@ def current_task(cls, loop=None):
5261
5362
None is returned when called not in the context of a Task.
5463
"""
64+
warnings.warn("Task.current_task() is deprecated, "
65+
"use asyncio.current_task() instead",
66+
PendingDeprecationWarning,
67+
stacklevel=2)
5568
if loop is None:
5669
loop = events.get_event_loop()
57-
return cls._current_tasks.get(loop)
70+
return current_task(loop)
5871

5972
@classmethod
6073
def all_tasks(cls, loop=None):
6174
"""Return a set of all tasks for an event loop.
6275
6376
By default all tasks for the current event loop are returned.
6477
"""
65-
if loop is None:
66-
loop = events.get_event_loop()
67-
return {t for t in cls._all_tasks if t._loop is loop}
78+
warnings.warn("Task.all_tasks() is deprecated, "
79+
"use asyncio.all_tasks() instead",
80+
PendingDeprecationWarning,
81+
stacklevel=2)
82+
return all_tasks(loop)
6883

6984
def __init__(self, coro, *, loop=None):
7085
super().__init__(loop=loop)
@@ -81,7 +96,7 @@ def __init__(self, coro, *, loop=None):
8196
self._coro = coro
8297

8398
self._loop.call_soon(self._step)
84-
self.__class__._all_tasks.add(self)
99+
_register_task(self._loop, self)
85100

86101
def __del__(self):
87102
if self._state == futures._PENDING and self._log_destroy_pending:
@@ -173,7 +188,7 @@ def _step(self, exc=None):
173188
coro = self._coro
174189
self._fut_waiter = None
175190

176-
self.__class__._current_tasks[self._loop] = self
191+
_enter_task(self._loop, self)
177192
# Call either coro.throw(exc) or coro.send(None).
178193
try:
179194
if exc is None:
@@ -237,7 +252,7 @@ def _step(self, exc=None):
237252
new_exc = RuntimeError(f'Task got bad yield: {result!r}')
238253
self._loop.call_soon(self._step, new_exc)
239254
finally:
240-
self.__class__._current_tasks.pop(self._loop)
255+
_leave_task(self._loop, self)
241256
self = None # Needed to break cycles when an exception occurs.
242257

243258
def _wakeup(self, future):
@@ -715,3 +730,61 @@ def callback():
715730

716731
loop.call_soon_threadsafe(callback)
717732
return future
733+
734+
735+
# WeakKeyDictionary of {Task: EventLoop} containing all tasks alive.
736+
# Task should be a weak reference to remove entry on task garbage
737+
# collection, EventLoop is required
738+
# to not access to private task._loop attribute.
739+
_all_tasks = weakref.WeakKeyDictionary()
740+
741+
# Dictionary containing tasks that are currently active in
742+
# all running event loops. {EventLoop: Task}
743+
_current_tasks = {}
744+
745+
746+
def _register_task(loop, task):
747+
"""Register a new task in asyncio as executed by loop.
748+
749+
Returns None.
750+
"""
751+
_all_tasks[task] = loop
752+
753+
754+
def _enter_task(loop, task):
755+
current_task = _current_tasks.get(loop)
756+
if current_task is not None:
757+
raise RuntimeError(f"Cannot enter into task {task!r} while another "
758+
f"task {current_task!r} is being executed.")
759+
_current_tasks[loop] = task
760+
761+
762+
def _leave_task(loop, task):
763+
current_task = _current_tasks.get(loop)
764+
if current_task is not task:
765+
raise RuntimeError(f"Leaving task {task!r} does not match "
766+
f"the current task {current_task!r}.")
767+
del _current_tasks[loop]
768+
769+
770+
def _unregister_task(loop, task):
771+
_all_tasks.pop(task, None)
772+
773+
774+
_py_register_task = _register_task
775+
_py_unregister_task = _unregister_task
776+
_py_enter_task = _enter_task
777+
_py_leave_task = _leave_task
778+
779+
780+
try:
781+
from _asyncio import (_register_task, _unregister_task,
782+
_enter_task, _leave_task,
783+
_all_tasks, _current_tasks)
784+
except ImportError:
785+
pass
786+
else:
787+
_c_register_task = _register_task
788+
_c_unregister_task = _unregister_task
789+
_c_enter_task = _enter_task
790+
_c_leave_task = _leave_task

0 commit comments

Comments
 (0)