Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
71c327e
Proof of concept
asvetlov Dec 11, 2017
a384a51
Move functions into base_tasks.py
asvetlov Dec 11, 2017
c9647a7
Use WeakKeyDictionary for _all_tasks
asvetlov Dec 11, 2017
9888405
Polish implementation
asvetlov Dec 12, 2017
8897fd8
Merge remote-tracking branch 'upstream/master' into current_task2
asvetlov Dec 12, 2017
a4d01b8
Fix C Acceleration
asvetlov Dec 12, 2017
8bf63f3
Add tests for deprecation
asvetlov Dec 12, 2017
9d205dd
Make C Accelerators for _register_task, _enter_task and _leave_task
asvetlov Dec 12, 2017
96ba687
Move pythod fallbacks back to base_tasks.py
asvetlov Dec 12, 2017
dda83b1
Add tests
asvetlov Dec 12, 2017
5a8e598
Add NEWS entry
asvetlov Dec 12, 2017
c3b1794
Fix test name duplication.
asvetlov Dec 12, 2017
3889ac0
Address comments
asvetlov Dec 13, 2017
df7ac0b
Add new tests
asvetlov Dec 13, 2017
a19c67f
Improve C Accelerator
asvetlov Dec 13, 2017
998f316
Improve tests
asvetlov Dec 14, 2017
a36ca8a
Merge remote-tracking branch 'upstream/master' into current_task2
asvetlov Dec 14, 2017
5b1cfc2
Reorganize imports
asvetlov Dec 14, 2017
4cdfd0a
Refactor code
asvetlov Dec 14, 2017
ff5cf49
Merge remote-tracking branch 'upstream/master' into current_task2
asvetlov Dec 15, 2017
1c7f5c2
Add missing decref
asvetlov Dec 15, 2017
9abbb89
Reorganize code
asvetlov Dec 15, 2017
8430283
Add docs/docstrings
asvetlov Dec 15, 2017
5fdc96a
Merge remote-tracking branch 'upstream/master' into current_task2
asvetlov Dec 15, 2017
ee26922
Merge remote-tracking branch 'upstream/master' into current_task2
asvetlov Dec 16, 2017
21cbb06
Fix notes
asvetlov Dec 16, 2017
85059e5
Fix hash calculation errors
asvetlov Dec 16, 2017
06a57d8
Fix markup
asvetlov Dec 16, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,28 @@ Task functions
the event loop object used by the underlying task or coroutine. If it's
not provided, the default event loop is used.


.. function:: current_task(loop=None):

Return the current running :class:`Task` instance or ``None``, if
no task is running.

If *loop* is ``None`` :func:`get_running_loop` is used to get
the current loop.

.. versionadded:: 3.7


.. function:: all_tasks(loop=None):

Return a set of :class:`Task` objects created for the loop.

If *loop* is ``None`` :func:`get_event_loop` is used for getting
current loop.

.. versionadded:: 3.7


.. function:: as_completed(fs, \*, loop=None, timeout=None)

Return an iterator whose values, when waited for, are :class:`Future`
Expand Down
101 changes: 87 additions & 14 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED',
'wait', 'wait_for', 'as_completed', 'sleep',
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
'current_task', 'all_tasks',
'_register_task', '_unregister_task', '_enter_task', '_leave_task',
)

import concurrent.futures
Expand All @@ -21,6 +23,20 @@
from .coroutines import coroutine


def current_task(loop=None):
"""Return a currently executed task."""
if loop is None:
loop = events.get_running_loop()
return _current_tasks.get(loop)


def all_tasks(loop=None):
"""Return a set of all tasks for the loop."""
if loop is None:
loop = events.get_event_loop()
return {t for t, l in _all_tasks.items() if l is loop}


class Task(futures.Future):
"""A coroutine wrapped in a Future."""

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

# Weak set containing all tasks alive.
_all_tasks = weakref.WeakSet()

# Dictionary containing tasks that are currently active in
# all running event loops. {EventLoop: Task}
_current_tasks = {}

# If False, don't log a message if the task is destroyed whereas its
# status is still pending
_log_destroy_pending = True
Expand All @@ -52,19 +61,25 @@ def current_task(cls, loop=None):

None is returned when called not in the context of a Task.
"""
warnings.warn("Task.current_task() is deprecated, "
"use asyncio.current_task() instead",
PendingDeprecationWarning,
stacklevel=2)
if loop is None:
loop = events.get_event_loop()
return cls._current_tasks.get(loop)
return current_task(loop)

@classmethod
def all_tasks(cls, loop=None):
"""Return a set of all tasks for an event loop.

By default all tasks for the current event loop are returned.
"""
if loop is None:
loop = events.get_event_loop()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should keep get_event_loop() here for backwards compatibility. So that code that calls Task.all_tasks() before a loop is created continues to work in 3.7 (although it will raise a warning).

asyncio.all_tasks() should use get_running_loop().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about asyncio.all_tasks() -- calling the function outside of coroutine context might make sense.
RuntimeError from asyncio.current_task() is more reasonable but I not sure again.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about asyncio.all_tasks() -- calling the function outside of coroutine context might make sense.

Then pass a loop object explicitly. The problem with get_event_loop() is that it can create a new loop object implicitly. Or it may fail at creating it -- that function is weird.

return {t for t in cls._all_tasks if t._loop is loop}
warnings.warn("Task.all_tasks() is deprecated, "
"use asyncio.all_tasks() instead",
PendingDeprecationWarning,
stacklevel=2)
return all_tasks(loop)

def __init__(self, coro, *, loop=None):
super().__init__(loop=loop)
Expand All @@ -81,7 +96,7 @@ def __init__(self, coro, *, loop=None):
self._coro = coro

self._loop.call_soon(self._step)
self.__class__._all_tasks.add(self)
_register_task(self._loop, self)

def __del__(self):
if self._state == futures._PENDING and self._log_destroy_pending:
Expand Down Expand Up @@ -173,7 +188,7 @@ def _step(self, exc=None):
coro = self._coro
self._fut_waiter = None

self.__class__._current_tasks[self._loop] = self
_enter_task(self._loop, self)
# Call either coro.throw(exc) or coro.send(None).
try:
if exc is None:
Expand Down Expand Up @@ -237,7 +252,7 @@ def _step(self, exc=None):
new_exc = RuntimeError(f'Task got bad yield: {result!r}')
self._loop.call_soon(self._step, new_exc)
finally:
self.__class__._current_tasks.pop(self._loop)
_leave_task(self._loop, self)
self = None # Needed to break cycles when an exception occurs.

def _wakeup(self, future):
Expand Down Expand Up @@ -715,3 +730,61 @@ def callback():

loop.call_soon_threadsafe(callback)
return future


# WeakKeyDictionary of {Task: EventLoop} containing all tasks alive.
# Task should be a weak reference to remove entry on task garbage
# collection, EventLoop is required
# to not access to private task._loop attribute.
_all_tasks = weakref.WeakKeyDictionary()

# Dictionary containing tasks that are currently active in
# all running event loops. {EventLoop: Task}
_current_tasks = {}


def _register_task(loop, task):
"""Register a new task in asyncio as executed by loop.

Returns None.
"""
_all_tasks[task] = loop


def _enter_task(loop, task):
current_task = _current_tasks.get(loop)
if current_task is not None:
raise RuntimeError(f"Cannot enter into task {task!r} while another "
f"task {current_task!r} is being executed.")
_current_tasks[loop] = task


def _leave_task(loop, task):
current_task = _current_tasks.get(loop)
if current_task is not task:
raise RuntimeError(f"Leaving task {task!r} does not match "
f"the current task {current_task!r}.")
del _current_tasks[loop]


def _unregister_task(loop, task):
_all_tasks.pop(task, None)


_py_register_task = _register_task
_py_unregister_task = _unregister_task
_py_enter_task = _enter_task
_py_leave_task = _leave_task


try:
from _asyncio import (_register_task, _unregister_task,
_enter_task, _leave_task,
_all_tasks, _current_tasks)
except ImportError:
pass
else:
_c_register_task = _register_task
_c_unregister_task = _unregister_task
_c_enter_task = _enter_task
_c_leave_task = _leave_task
Loading