Tasks
-----
-.. method:: AbstractEventLoop.create_task(coro)
+.. method:: AbstractEventLoop.create_task(coro, \*, name=None)
Schedule the execution of a :ref:`coroutine object <coroutine>`: wrap it in
a future. Return a :class:`Task` object.
interoperability. In this case, the result type is a subclass of
:class:`Task`.
+ If the *name* argument is provided and not ``None``, it is set as the name
+ of the task using :meth:`Task.set_name`.
+
.. versionadded:: 3.4.2
+ .. versionchanged:: 3.8
+ Added the ``name`` parameter.
+
.. method:: AbstractEventLoop.set_task_factory(factory)
Set a task factory that will be used by
Task
----
-.. function:: create_task(coro)
+.. function:: create_task(coro, \*, name=None)
Wrap a :ref:`coroutine <coroutine>` *coro* into a task and schedule
- its execution. Return the task object.
+ its execution. Return the task object.
+
+ If *name* is not ``None``, it is set as the name of the task using
+ :meth:`Task.set_name`.
The task is executed in :func:`get_running_loop` context,
:exc:`RuntimeError` is raised if there is no running loop in
.. versionadded:: 3.7
-.. class:: Task(coro, \*, loop=None)
+ .. versionchanged:: 3.8
+ Added the ``name`` parameter.
+
+.. class:: Task(coro, \*, loop=None, name=None)
A unit for concurrent running of :ref:`coroutines <coroutine>`,
subclass of :class:`Future`.
.. versionchanged:: 3.7
Added support for the :mod:`contextvars` module.
+ .. versionchanged:: 3.8
+ Added the ``name`` parameter.
+
.. classmethod:: all_tasks(loop=None)
Return a set of all tasks for an event loop.
get_stack(). The file argument is an I/O stream to which the output
is written; by default output is written to sys.stderr.
+ .. method:: get_name()
+
+ Return the name of the task.
+
+ If no name has been explicitly assigned to the task, the default
+ ``Task`` implementation generates a default name during instantiation.
+
+ .. versionadded:: 3.8
+
+ .. method:: set_name(value)
+
+ Set the name of the task.
+
+ The *value* argument can be any object, which is then converted to a
+ string.
+
+ In the default ``Task`` implementation, the name will be visible in the
+ :func:`repr` output of a task object.
+
+ .. versionadded:: 3.8
+
Example: Parallel execution of tasks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``PyGC_Head`` struct is changed completely. All code touched the
struct member should be rewritten. (See :issue:`33597`)
+* Asyncio tasks can now be named, either by passing the ``name`` keyword
+ argument to :func:`asyncio.create_task` or
+ the :meth:`~asyncio.AbstractEventLoop.create_task` event loop method, or by
+ calling the :meth:`~asyncio.Task.set_name` method on the task object. The
+ task name is visible in the ``repr()`` output of :class:`asyncio.Task` and
+ can also be retrieved using the :meth:`~asyncio.Task.get_name` method.
+
CPython bytecode changes
------------------------
"""Create a Future object attached to the loop."""
return futures.Future(loop=self)
- def create_task(self, coro):
+ def create_task(self, coro, *, name=None):
"""Schedule a coroutine object.
Return a task object.
"""
self._check_closed()
if self._task_factory is None:
- task = tasks.Task(coro, loop=self)
+ task = tasks.Task(coro, loop=self, name=name)
if task._source_traceback:
del task._source_traceback[-1]
else:
task = self._task_factory(self, coro)
+ tasks._set_task_name(task, name)
+
return task
def set_task_factory(self, factory):
# replace status
info[0] = 'cancelling'
+ info.insert(1, 'name=%r' % task.get_name())
+
coro = coroutines._format_coroutine(task._coro)
- info.insert(1, f'coro=<{coro}>')
+ info.insert(2, f'coro=<{coro}>')
if task._fut_waiter is not None:
- info.insert(2, f'wait_for={task._fut_waiter!r}')
+ info.insert(3, f'wait_for={task._fut_waiter!r}')
return info
# Method scheduling a coroutine object: create a task.
- def create_task(self, coro):
+ def create_task(self, coro, *, name=None):
raise NotImplementedError
# Methods for interacting with threads.
import contextvars
import functools
import inspect
+import itertools
import types
import warnings
import weakref
from . import futures
from .coroutines import coroutine
+# Helper to generate new task names
+# This uses itertools.count() instead of a "+= 1" operation because the latter
+# is not thread safe. See bpo-11866 for a longer explanation.
+_task_name_counter = itertools.count(1).__next__
+
def current_task(loop=None):
"""Return a currently executed task."""
return {t for t in _all_tasks if futures._get_loop(t) is loop}
+def _set_task_name(task, name):
+ if name is not None:
+ try:
+ set_name = task.set_name
+ except AttributeError:
+ pass
+ else:
+ set_name(name)
+
+
class Task(futures._PyFuture): # Inherit Python Task implementation
# from a Python Future implementation.
stacklevel=2)
return _all_tasks_compat(loop)
- def __init__(self, coro, *, loop=None):
+ def __init__(self, coro, *, loop=None, name=None):
super().__init__(loop=loop)
if self._source_traceback:
del self._source_traceback[-1]
self._log_destroy_pending = False
raise TypeError(f"a coroutine was expected, got {coro!r}")
+ if name is None:
+ self._name = f'Task-{_task_name_counter()}'
+ else:
+ self._name = str(name)
+
self._must_cancel = False
self._fut_waiter = None
self._coro = coro
def _repr_info(self):
return base_tasks._task_repr_info(self)
+ def get_name(self):
+ return self._name
+
+ def set_name(self, value):
+ self._name = str(value)
+
def set_result(self, result):
raise RuntimeError('Task does not support set_result operation')
Task = _CTask = _asyncio.Task
-def create_task(coro):
+def create_task(coro, *, name=None):
"""Schedule the execution of a coroutine object in a spawn task.
Return a Task object.
"""
loop = events.get_running_loop()
- return loop.create_task(coro)
+ task = loop.create_task(coro)
+ _set_task_name(task, name)
+ return task
# wait() and as_completed() similar to those in PEP 3148.
task._log_destroy_pending = False
coro.close()
+ def test_create_named_task_with_default_factory(self):
+ async def test():
+ pass
+
+ loop = asyncio.new_event_loop()
+ task = loop.create_task(test(), name='test_task')
+ try:
+ self.assertEqual(task.get_name(), 'test_task')
+ finally:
+ loop.run_until_complete(task)
+ loop.close()
+
+ def test_create_named_task_with_custom_factory(self):
+ def task_factory(loop, coro):
+ return asyncio.Task(coro, loop=loop)
+
+ async def test():
+ pass
+
+ loop = asyncio.new_event_loop()
+ loop.set_task_factory(task_factory)
+ task = loop.create_task(test(), name='test_task')
+ try:
+ self.assertEqual(task.get_name(), 'test_task')
+ finally:
+ loop.run_until_complete(task)
+ loop.close()
+
def test_run_forever_keyboard_interrupt(self):
# Python issue #22601: ensure that the temporary task created by
# run_forever() consumes the KeyboardInterrupt and so don't log
Task = None
Future = None
- def new_task(self, loop, coro):
- return self.__class__.Task(coro, loop=loop)
+ def new_task(self, loop, coro, name='TestTask'):
+ return self.__class__.Task(coro, loop=loop, name=name)
def new_future(self, loop):
return self.__class__.Future(loop=loop)
coro = format_coroutine(coro_qualname, 'running', src,
t._source_traceback, generator=True)
self.assertEqual(repr(t),
- '<Task pending %s cb=[<Dummy>()]>' % coro)
+ "<Task pending name='TestTask' %s cb=[<Dummy>()]>" % coro)
# test cancelling Task
t.cancel() # Does not take immediate effect!
self.assertEqual(repr(t),
- '<Task cancelling %s cb=[<Dummy>()]>' % coro)
+ "<Task cancelling name='TestTask' %s cb=[<Dummy>()]>" % coro)
# test cancelled Task
self.assertRaises(asyncio.CancelledError,
coro = format_coroutine(coro_qualname, 'done', src,
t._source_traceback)
self.assertEqual(repr(t),
- '<Task cancelled %s>' % coro)
+ "<Task cancelled name='TestTask' %s>" % coro)
# test finished Task
t = self.new_task(self.loop, notmuch())
coro = format_coroutine(coro_qualname, 'done', src,
t._source_traceback)
self.assertEqual(repr(t),
- "<Task finished %s result='abc'>" % coro)
+ "<Task finished name='TestTask' %s result='abc'>" % coro)
+
+ def test_task_repr_autogenerated(self):
+ @asyncio.coroutine
+ def notmuch():
+ return 123
+
+ t1 = self.new_task(self.loop, notmuch(), None)
+ t2 = self.new_task(self.loop, notmuch(), None)
+ self.assertNotEqual(repr(t1), repr(t2))
+
+ match1 = re.match("^<Task pending name='Task-(\d+)'", repr(t1))
+ self.assertIsNotNone(match1)
+ match2 = re.match("^<Task pending name='Task-(\d+)'", repr(t2))
+ self.assertIsNotNone(match2)
+
+ # Autogenerated task names should have monotonically increasing numbers
+ self.assertLess(int(match1.group(1)), int(match2.group(1)))
+ self.loop.run_until_complete(t1)
+ self.loop.run_until_complete(t2)
+
+ def test_task_repr_name_not_str(self):
+ @asyncio.coroutine
+ def notmuch():
+ return 123
+
+ t = self.new_task(self.loop, notmuch())
+ t.set_name({6})
+ self.assertEqual(t.get_name(), '{6}')
+ self.loop.run_until_complete(t)
def test_task_repr_coro_decorator(self):
self.loop.set_debug(False)
t._source_traceback,
generator=not coroutines._DEBUG)
self.assertEqual(repr(t),
- '<Task pending %s cb=[<Dummy>()]>' % coro)
+ "<Task pending name='TestTask' %s cb=[<Dummy>()]>" % coro)
self.loop.run_until_complete(t)
def test_task_repr_wait_for(self):
self.loop.run_until_complete(coro())
+ def test_bare_create_named_task(self):
+
+ async def coro_noop():
+ pass
+
+ async def coro():
+ task = asyncio.create_task(coro_noop(), name='No-op')
+ self.assertEqual(task.get_name(), 'No-op')
+ await task
+
+ self.loop.run_until_complete(coro())
+
def test_context_1(self):
cvar = contextvars.ContextVar('cvar', default='nope')
Hans de Graaff
Tim Graham
Kim Gräsman
+Alex Grönholm
Nathaniel Gray
Eddy De Greef
Duane Griffin
Lars Gustäbel
Thomas Güttler
Jonas H.
+Antti Haapala
Joseph Hackman
Barry Haddow
Philipp Hagemeister
--- /dev/null
+The default asyncio task class now always has a name which can be get or set
+using two new methods (:meth:`~asyncio.Task.get_name()` and
+:meth:`~asyncio.Task.set_name`) and is visible in the :func:`repr` output. An
+initial name can also be set using the new ``name`` keyword argument to
+:func:`asyncio.create_task` or the
+:meth:`~asyncio.AbstractEventLoop.create_task` method of the event loop.
+If no initial name is set, the default Task implementation generates a name
+like ``Task-1`` using a monotonic counter.
static PyObject *cached_running_holder;
static volatile uint64_t cached_running_holder_tsid;
+/* Counter for autogenerated Task names */
+static uint64_t task_name_counter = 0;
/* WeakSet containing all alive tasks. */
static PyObject *all_tasks;
FutureObj_HEAD(task)
PyObject *task_fut_waiter;
PyObject *task_coro;
+ PyObject *task_name;
PyContext *task_context;
int task_must_cancel;
int task_log_destroy_pending;
coro: object
*
loop: object = None
+ name: object = None
A coroutine wrapped in a Future.
[clinic start generated code]*/
static int
-_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop)
-/*[clinic end generated code: output=9f24774c2287fc2f input=8d132974b049593e]*/
+_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
+ PyObject *name)
+/*[clinic end generated code: output=88b12b83d570df50 input=352a3137fe60091d]*/
{
if (future_init((FutureObj*)self, loop)) {
return -1;
Py_INCREF(coro);
Py_XSETREF(self->task_coro, coro);
+ if (name == Py_None) {
+ name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter);
+ } else if (!PyUnicode_Check(name)) {
+ name = PyObject_Str(name);
+ } else {
+ Py_INCREF(name);
+ }
+ Py_XSETREF(self->task_name, name);
+ if (self->task_name == NULL) {
+ return -1;
+ }
+
if (task_call_step_soon(self, NULL)) {
return -1;
}
(void)FutureObj_clear((FutureObj*) task);
Py_CLEAR(task->task_context);
Py_CLEAR(task->task_coro);
+ Py_CLEAR(task->task_name);
Py_CLEAR(task->task_fut_waiter);
return 0;
}
{
Py_VISIT(task->task_context);
Py_VISIT(task->task_coro);
+ Py_VISIT(task->task_name);
Py_VISIT(task->task_fut_waiter);
(void)FutureObj_traverse((FutureObj*) task, visit, arg);
return 0;
return NULL;
}
+/*[clinic input]
+_asyncio.Task.get_name
+[clinic start generated code]*/
+
+static PyObject *
+_asyncio_Task_get_name_impl(TaskObj *self)
+/*[clinic end generated code: output=0ecf1570c3b37a8f input=a4a6595d12f4f0f8]*/
+{
+ if (self->task_name) {
+ Py_INCREF(self->task_name);
+ return self->task_name;
+ }
+
+ Py_RETURN_NONE;
+}
+
+/*[clinic input]
+_asyncio.Task.set_name
+
+ value: object
+ /
+[clinic start generated code]*/
+
+static PyObject *
+_asyncio_Task_set_name(TaskObj *self, PyObject *value)
+/*[clinic end generated code: output=138a8d51e32057d6 input=a8359b6e65f8fd31]*/
+{
+ PyObject *name = PyObject_Str(value);
+ if (name == NULL) {
+ return NULL;
+ }
+
+ Py_XSETREF(self->task_name, name);
+ Py_RETURN_NONE;
+}
static void
TaskObj_finalize(TaskObj *task)
_ASYNCIO_TASK_GET_STACK_METHODDEF
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
_ASYNCIO_TASK__REPR_INFO_METHODDEF
+ _ASYNCIO_TASK_GET_NAME_METHODDEF
+ _ASYNCIO_TASK_SET_NAME_METHODDEF
{NULL, NULL} /* Sentinel */
};
}
PyDoc_STRVAR(_asyncio_Task___init____doc__,
-"Task(coro, *, loop=None)\n"
+"Task(coro, *, loop=None, name=None)\n"
"--\n"
"\n"
"A coroutine wrapped in a Future.");
static int
-_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop);
+_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
+ PyObject *name);
static int
_asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
{
int return_value = -1;
- static const char * const _keywords[] = {"coro", "loop", NULL};
- static _PyArg_Parser _parser = {"O|$O:Task", _keywords, 0};
+ static const char * const _keywords[] = {"coro", "loop", "name", NULL};
+ static _PyArg_Parser _parser = {"O|$OO:Task", _keywords, 0};
PyObject *coro;
PyObject *loop = Py_None;
+ PyObject *name = Py_None;
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
- &coro, &loop)) {
+ &coro, &loop, &name)) {
goto exit;
}
- return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop);
+ return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name);
exit:
return return_value;
#define _ASYNCIO_TASK_SET_EXCEPTION_METHODDEF \
{"set_exception", (PyCFunction)_asyncio_Task_set_exception, METH_O, _asyncio_Task_set_exception__doc__},
+PyDoc_STRVAR(_asyncio_Task_get_name__doc__,
+"get_name($self, /)\n"
+"--\n"
+"\n");
+
+#define _ASYNCIO_TASK_GET_NAME_METHODDEF \
+ {"get_name", (PyCFunction)_asyncio_Task_get_name, METH_NOARGS, _asyncio_Task_get_name__doc__},
+
+static PyObject *
+_asyncio_Task_get_name_impl(TaskObj *self);
+
+static PyObject *
+_asyncio_Task_get_name(TaskObj *self, PyObject *Py_UNUSED(ignored))
+{
+ return _asyncio_Task_get_name_impl(self);
+}
+
+PyDoc_STRVAR(_asyncio_Task_set_name__doc__,
+"set_name($self, value, /)\n"
+"--\n"
+"\n");
+
+#define _ASYNCIO_TASK_SET_NAME_METHODDEF \
+ {"set_name", (PyCFunction)_asyncio_Task_set_name, METH_O, _asyncio_Task_set_name__doc__},
+
PyDoc_STRVAR(_asyncio__get_running_loop__doc__,
"_get_running_loop($module, /)\n"
"--\n"
exit:
return return_value;
}
-/*[clinic end generated code: output=b6148b0134e7a819 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=67da879c9f841505 input=a9049054013a1b77]*/