| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 52dee68 commit cca4eec
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -246,7 +246,7 @@ Futures | |||
| 246 | 246 | Tasks | |
| 247 | 247 | ----- | |
| 248 | 248 | ||
| 249 | - .. method:: AbstractEventLoop.create_task(coro) | ||
| 249 | + .. method:: AbstractEventLoop.create_task(coro, \*, name=None) | ||
| 250 | 250 | ||
| 251 | 251 | Schedule the execution of a :ref:`coroutine object <coroutine>`: wrap it in | |
| 252 | 252 | a future. Return a :class:`Task` object. | |
@@ -255,8 +255,14 @@ Tasks | |||
| 255 | 255 | interoperability. In this case, the result type is a subclass of | |
| 256 | 256 | :class:`Task`. | |
| 257 | 257 | ||
| 258 | + If the *name* argument is provided and not ``None``, it is set as the name | ||
| 259 | + of the task using :meth:`Task.set_name`. | ||
| 260 | + | ||
| 258 | 261 | .. versionadded:: 3.4.2 | |
| 259 | 262 | ||
| 263 | + .. versionchanged:: 3.8 | ||
| 264 | + Added the ``name`` parameter. | ||
| 265 | + | ||
| 260 | 266 | .. method:: AbstractEventLoop.set_task_factory(factory) | |
| 261 | 267 | ||
| 262 | 268 | Set a task factory that will be used by | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -387,18 +387,24 @@ with the result. | |||
| 387 | 387 | Task | |
| 388 | 388 | ---- | |
| 389 | 389 | ||
| 390 | - .. function:: create_task(coro) | ||
| 390 | + .. function:: create_task(coro, \*, name=None) | ||
| 391 | 391 | ||
| 392 | 392 | Wrap a :ref:`coroutine <coroutine>` *coro* into a task and schedule | |
| 393 | - its execution. Return the task object. | ||
| 393 | + its execution. Return the task object. | ||
| 394 | + | ||
| 395 | + If *name* is not ``None``, it is set as the name of the task using | ||
| 396 | + :meth:`Task.set_name`. | ||
| 394 | 397 | ||
| 395 | 398 | The task is executed in :func:`get_running_loop` context, | |
| 396 | 399 | :exc:`RuntimeError` is raised if there is no running loop in | |
| 397 | 400 | current thread. | |
| 398 | 401 | ||
| 399 | 402 | .. versionadded:: 3.7 | |
| 400 | 403 | ||
| 401 | - .. class:: Task(coro, \*, loop=None) | ||
| 404 | + .. versionchanged:: 3.8 | ||
| 405 | + Added the ``name`` parameter. | ||
| 406 | + | ||
| 407 | + .. class:: Task(coro, \*, loop=None, name=None) | ||
| 402 | 408 | ||
| 403 | 409 | A unit for concurrent running of :ref:`coroutines <coroutine>`, | |
| 404 | 410 | subclass of :class:`Future`. | |
@@ -438,6 +444,9 @@ Task | |||
| 438 | 444 | .. versionchanged:: 3.7 | |
| 439 | 445 | Added support for the :mod:`contextvars` module. | |
| 440 | 446 | ||
| 447 | + .. versionchanged:: 3.8 | ||
| 448 | + Added the ``name`` parameter. | ||
| 449 | + | ||
| 441 | 450 | .. classmethod:: all_tasks(loop=None) | |
| 442 | 451 | ||
| 443 | 452 | Return a set of all tasks for an event loop. | |
@@ -504,6 +513,27 @@ Task | |||
| 504 | 513 | get_stack(). The file argument is an I/O stream to which the output | |
| 505 | 514 | is written; by default output is written to sys.stderr. | |
| 506 | 515 | ||
| 516 | + .. method:: get_name() | ||
| 517 | + | ||
| 518 | + Return the name of the task. | ||
| 519 | + | ||
| 520 | + If no name has been explicitly assigned to the task, the default | ||
| 521 | + ``Task`` implementation generates a default name during instantiation. | ||
| 522 | + | ||
| 523 | + .. versionadded:: 3.8 | ||
| 524 | + | ||
| 525 | + .. method:: set_name(value) | ||
| 526 | + | ||
| 527 | + Set the name of the task. | ||
| 528 | + | ||
| 529 | + The *value* argument can be any object, which is then converted to a | ||
| 530 | + string. | ||
| 531 | + | ||
| 532 | + In the default ``Task`` implementation, the name will be visible in the | ||
| 533 | + :func:`repr` output of a task object. | ||
| 534 | + | ||
| 535 | + .. versionadded:: 3.8 | ||
| 536 | + | ||
| 507 | 537 | ||
| 508 | 538 | Example: Parallel execution of tasks | |
| 509 | 539 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -249,6 +249,13 @@ Changes in the Python API | |||
| 249 | 249 | * ``PyGC_Head`` struct is changed completely. All code touched the | |
| 250 | 250 | struct member should be rewritten. (See :issue:`33597`) | |
| 251 | 251 | ||
| 252 | + * Asyncio tasks can now be named, either by passing the ``name`` keyword | ||
| 253 | + argument to :func:`asyncio.create_task` or | ||
| 254 | + the :meth:`~asyncio.AbstractEventLoop.create_task` event loop method, or by | ||
| 255 | + calling the :meth:`~asyncio.Task.set_name` method on the task object. The | ||
| 256 | + task name is visible in the ``repr()`` output of :class:`asyncio.Task` and | ||
| 257 | + can also be retrieved using the :meth:`~asyncio.Task.get_name` method. | ||
| 258 | + | ||
| 252 | 259 | ||
| 253 | 260 | CPython bytecode changes | |
| 254 | 261 | ------------------------ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -384,18 +384,20 @@ def create_future(self): | |||
| 384 | 384 | """Create a Future object attached to the loop.""" | |
| 385 | 385 | return futures.Future(loop=self) | |
| 386 | 386 | ||
| 387 | - def create_task(self, coro): | ||
| 387 | + def create_task(self, coro, *, name=None): | ||
| 388 | 388 | """Schedule a coroutine object. | |
| 389 | 389 | ||
| 390 | 390 | Return a task object. | |
| 391 | 391 | """ | |
| 392 | 392 | self._check_closed() | |
| 393 | 393 | if self._task_factory is None: | |
| 394 | - task = tasks.Task(coro, loop=self) | ||
| 394 | + task = tasks.Task(coro, loop=self, name=name) | ||
| 395 | 395 | if task._source_traceback: | |
| 396 | 396 | del task._source_traceback[-1] | |
| 397 | 397 | else: | |
| 398 | 398 | task = self._task_factory(self, coro) | |
| 399 | + tasks._set_task_name(task, name) | ||
| 400 | + | ||
| 399 | 401 | return task | |
| 400 | 402 | ||
| 401 | 403 | def set_task_factory(self, factory): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,11 +12,13 @@ def _task_repr_info(task): | |||
| 12 | 12 | # replace status | |
| 13 | 13 | info[0] = 'cancelling' | |
| 14 | 14 | ||
| 15 | + info.insert(1, 'name=%r' % task.get_name()) | ||
| 16 | + | ||
| 15 | 17 | coro = coroutines._format_coroutine(task._coro) | |
| 16 | - info.insert(1, f'coro=<{coro}>') | ||
| 18 | + info.insert(2, f'coro=<{coro}>') | ||
| 17 | 19 | ||
| 18 | 20 | if task._fut_waiter is not None: | |
| 19 | - info.insert(2, f'wait_for={task._fut_waiter!r}') | ||
| 21 | + info.insert(3, f'wait_for={task._fut_waiter!r}') | ||
| 20 | 22 | return info | |
| 21 | 23 | ||
| 22 | 24 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -277,7 +277,7 @@ def create_future(self): | |||
| 277 | 277 | ||
| 278 | 278 | # Method scheduling a coroutine object: create a task. | |
| 279 | 279 | ||
| 280 | - def create_task(self, coro): | ||
| 280 | + def create_task(self, coro, *, name=None): | ||
| 281 | 281 | raise NotImplementedError | |
| 282 | 282 | ||
| 283 | 283 | # Methods for interacting with threads. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,7 @@ | |||
| 13 | 13 | import contextvars | |
| 14 | 14 | import functools | |
| 15 | 15 | import inspect | |
| 16 | + import itertools | ||
| 16 | 17 | import types | |
| 17 | 18 | import warnings | |
| 18 | 19 | import weakref | |
@@ -23,6 +24,11 @@ | |||
| 23 | 24 | from . import futures | |
| 24 | 25 | from .coroutines import coroutine | |
| 25 | 26 | ||
| 27 | + # Helper to generate new task names | ||
| 28 | + # This uses itertools.count() instead of a "+= 1" operation because the latter | ||
| 29 | + # is not thread safe. See bpo-11866 for a longer explanation. | ||
| 30 | + _task_name_counter = itertools.count(1).__next__ | ||
| 31 | + | ||
| 26 | 32 | ||
| 27 | 33 | def current_task(loop=None): | |
| 28 | 34 | """Return a currently executed task.""" | |
@@ -48,6 +54,16 @@ def _all_tasks_compat(loop=None): | |||
| 48 | 54 | return {t for t in _all_tasks if futures._get_loop(t) is loop} | |
| 49 | 55 | ||
| 50 | 56 | ||
| 57 | + def _set_task_name(task, name): | ||
| 58 | + if name is not None: | ||
| 59 | + try: | ||
| 60 | + set_name = task.set_name | ||
| 61 | + except AttributeError: | ||
| 62 | + pass | ||
| 63 | + else: | ||
| 64 | + set_name(name) | ||
| 65 | + | ||
| 66 | + | ||
| 51 | 67 | class Task(futures._PyFuture): # Inherit Python Task implementation | |
| 52 | 68 | # from a Python Future implementation. | |
| 53 | 69 | ||
@@ -94,7 +110,7 @@ def all_tasks(cls, loop=None): | |||
| 94 | 110 | stacklevel=2) | |
| 95 | 111 | return _all_tasks_compat(loop) | |
| 96 | 112 | ||
| 97 | - def __init__(self, coro, *, loop=None): | ||
| 113 | + def __init__(self, coro, *, loop=None, name=None): | ||
| 98 | 114 | super().__init__(loop=loop) | |
| 99 | 115 | if self._source_traceback: | |
| 100 | 116 | del self._source_traceback[-1] | |
@@ -104,6 +120,11 @@ def __init__(self, coro, *, loop=None): | |||
| 104 | 120 | self._log_destroy_pending = False | |
| 105 | 121 | raise TypeError(f"a coroutine was expected, got {coro!r}") | |
| 106 | 122 | ||
| 123 | + if name is None: | ||
| 124 | + self._name = f'Task-{_task_name_counter()}' | ||
| 125 | + else: | ||
| 126 | + self._name = str(name) | ||
| 127 | + | ||
| 107 | 128 | self._must_cancel = False | |
| 108 | 129 | self._fut_waiter = None | |
| 109 | 130 | self._coro = coro | |
@@ -126,6 +147,12 @@ def __del__(self): | |||
| 126 | 147 | def _repr_info(self): | |
| 127 | 148 | return base_tasks._task_repr_info(self) | |
| 128 | 149 | ||
| 150 | + def get_name(self): | ||
| 151 | + return self._name | ||
| 152 | + | ||
| 153 | + def set_name(self, value): | ||
| 154 | + self._name = str(value) | ||
| 155 | + | ||
| 129 | 156 | def set_result(self, result): | |
| 130 | 157 | raise RuntimeError('Task does not support set_result operation') | |
| 131 | 158 | ||
@@ -312,13 +339,15 @@ def __wakeup(self, future): | |||
| 312 | 339 | Task = _CTask = _asyncio.Task | |
| 313 | 340 | ||
| 314 | 341 | ||
| 315 | - def create_task(coro): | ||
| 342 | + def create_task(coro, *, name=None): | ||
| 316 | 343 | """Schedule the execution of a coroutine object in a spawn task. | |
| 317 | 344 | ||
| 318 | 345 | Return a Task object. | |
| 319 | 346 | """ | |
| 320 | 347 | loop = events.get_running_loop() | |
| 321 | - return loop.create_task(coro) | ||
| 348 | + task = loop.create_task(coro) | ||
| 349 | + _set_task_name(task, name) | ||
| 350 | + return task | ||
| 322 | 351 | ||
| 323 | 352 | ||
| 324 | 353 | # wait() and as_completed() similar to those in PEP 3148. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -825,6 +825,34 @@ def create_task(self, coro): | |||
| 825 | 825 | task._log_destroy_pending = False | |
| 826 | 826 | coro.close() | |
| 827 | 827 | ||
| 828 | + def test_create_named_task_with_default_factory(self): | ||
| 829 | + async def test(): | ||
| 830 | + pass | ||
| 831 | + | ||
| 832 | + loop = asyncio.new_event_loop() | ||
| 833 | + task = loop.create_task(test(), name='test_task') | ||
| 834 | + try: | ||
| 835 | + self.assertEqual(task.get_name(), 'test_task') | ||
| 836 | + finally: | ||
| 837 | + loop.run_until_complete(task) | ||
| 838 | + loop.close() | ||
| 839 | + | ||
| 840 | + def test_create_named_task_with_custom_factory(self): | ||
| 841 | + def task_factory(loop, coro): | ||
| 842 | + return asyncio.Task(coro, loop=loop) | ||
| 843 | + | ||
| 844 | + async def test(): | ||
| 845 | + pass | ||
| 846 | + | ||
| 847 | + loop = asyncio.new_event_loop() | ||
| 848 | + loop.set_task_factory(task_factory) | ||
| 849 | + task = loop.create_task(test(), name='test_task') | ||
| 850 | + try: | ||
| 851 | + self.assertEqual(task.get_name(), 'test_task') | ||
| 852 | + finally: | ||
| 853 | + loop.run_until_complete(task) | ||
| 854 | + loop.close() | ||
| 855 | + | ||
| 828 | 856 | def test_run_forever_keyboard_interrupt(self): | |
| 829 | 857 | # Python issue #22601: ensure that the temporary task created by | |
| 830 | 858 | # run_forever() consumes the KeyboardInterrupt and so don't log | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -87,8 +87,8 @@ class BaseTaskTests: | |||
| 87 | 87 | Task = None | |
| 88 | 88 | Future = None | |
| 89 | 89 | ||
| 90 | - def new_task(self, loop, coro): | ||
| 91 | - return self.__class__.Task(coro, loop=loop) | ||
| 90 | + def new_task(self, loop, coro, name='TestTask'): | ||
| 91 | + return self.__class__.Task(coro, loop=loop, name=name) | ||
| 92 | 92 | ||
| 93 | 93 | def new_future(self, loop): | |
| 94 | 94 | return self.__class__.Future(loop=loop) | |
@@ -295,28 +295,57 @@ def notmuch(): | |||
| 295 | 295 | coro = format_coroutine(coro_qualname, 'running', src, | |
| 296 | 296 | t._source_traceback, generator=True) | |
| 297 | 297 | self.assertEqual(repr(t), | |
| 298 | - '<Task pending %s cb=[<Dummy>()]>' % coro) | ||
| 298 | + "<Task pending name='TestTask' %s cb=[<Dummy>()]>" % coro) | ||
| 299 | 299 | ||
| 300 | 300 | # test cancelling Task | |
| 301 | 301 | t.cancel() # Does not take immediate effect! | |
| 302 | 302 | self.assertEqual(repr(t), | |
| 303 | - '<Task cancelling %s cb=[<Dummy>()]>' % coro) | ||
| 303 | + "<Task cancelling name='TestTask' %s cb=[<Dummy>()]>" % coro) | ||
| 304 | 304 | ||
| 305 | 305 | # test cancelled Task | |
| 306 | 306 | self.assertRaises(asyncio.CancelledError, | |
| 307 | 307 | self.loop.run_until_complete, t) | |
| 308 | 308 | coro = format_coroutine(coro_qualname, 'done', src, | |
| 309 | 309 | t._source_traceback) | |
| 310 | 310 | self.assertEqual(repr(t), | |
| 311 | - '<Task cancelled %s>' % coro) | ||
| 311 | + "<Task cancelled name='TestTask' %s>" % coro) | ||
| 312 | 312 | ||
| 313 | 313 | # test finished Task | |
| 314 | 314 | t = self.new_task(self.loop, notmuch()) | |
| 315 | 315 | self.loop.run_until_complete(t) | |
| 316 | 316 | coro = format_coroutine(coro_qualname, 'done', src, | |
| 317 | 317 | t._source_traceback) | |
| 318 | 318 | self.assertEqual(repr(t), | |
| 319 | - "<Task finished %s result='abc'>" % coro) | ||
| 319 | + "<Task finished name='TestTask' %s result='abc'>" % coro) | ||
| 320 | + | ||
| 321 | + def test_task_repr_autogenerated(self): | ||
| 322 | + @asyncio.coroutine | ||
| 323 | + def notmuch(): | ||
| 324 | + return 123 | ||
| 325 | + | ||
| 326 | + t1 = self.new_task(self.loop, notmuch(), None) | ||
| 327 | + t2 = self.new_task(self.loop, notmuch(), None) | ||
| 328 | + self.assertNotEqual(repr(t1), repr(t2)) | ||
| 329 | + | ||
| 330 | + match1 = re.match("^<Task pending name='Task-(\d+)'", repr(t1)) | ||
| 331 | + self.assertIsNotNone(match1) | ||
| 332 | + match2 = re.match("^<Task pending name='Task-(\d+)'", repr(t2)) | ||
| 333 | + self.assertIsNotNone(match2) | ||
| 334 | + | ||
| 335 | + # Autogenerated task names should have monotonically increasing numbers | ||
| 336 | + self.assertLess(int(match1.group(1)), int(match2.group(1))) | ||
| 337 | + self.loop.run_until_complete(t1) | ||
| 338 | + self.loop.run_until_complete(t2) | ||
| 339 | + | ||
| 340 | + def test_task_repr_name_not_str(self): | ||
| 341 | + @asyncio.coroutine | ||
| 342 | + def notmuch(): | ||
| 343 | + return 123 | ||
| 344 | + | ||
| 345 | + t = self.new_task(self.loop, notmuch()) | ||
| 346 | + t.set_name({6}) | ||
| 347 | + self.assertEqual(t.get_name(), '{6}') | ||
| 348 | + self.loop.run_until_complete(t) | ||
| 320 | 349 | ||
| 321 | 350 | def test_task_repr_coro_decorator(self): | |
| 322 | 351 | self.loop.set_debug(False) | |
@@ -376,7 +405,7 @@ def notmuch(): | |||
| 376 | 405 | t._source_traceback, | |
| 377 | 406 | generator=not coroutines._DEBUG) | |
| 378 | 407 | self.assertEqual(repr(t), | |
| 379 | - '<Task pending %s cb=[<Dummy>()]>' % coro) | ||
| 408 | + "<Task pending name='TestTask' %s cb=[<Dummy>()]>" % coro) | ||
| 380 | 409 | self.loop.run_until_complete(t) | |
| 381 | 410 | ||
| 382 | 411 | def test_task_repr_wait_for(self): | |
@@ -2260,6 +2289,18 @@ async def coro(): | |||
| 2260 | 2289 | ||
| 2261 | 2290 | self.loop.run_until_complete(coro()) | |
| 2262 | 2291 | ||
| 2292 | + def test_bare_create_named_task(self): | ||
| 2293 | + | ||
| 2294 | + async def coro_noop(): | ||
| 2295 | + pass | ||
| 2296 | + | ||
| 2297 | + async def coro(): | ||
| 2298 | + task = asyncio.create_task(coro_noop(), name='No-op') | ||
| 2299 | + self.assertEqual(task.get_name(), 'No-op') | ||
| 2300 | + await task | ||
| 2301 | + | ||
| 2302 | + self.loop.run_until_complete(coro()) | ||
| 2303 | + | ||
| 2263 | 2304 | def test_context_1(self): | |
| 2264 | 2305 | cvar = contextvars.ContextVar('cvar', default='nope') | |
| 2265 | 2306 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -573,6 +573,7 @@ Elliot Gorokhovsky | |||
| 573 | 573 | Hans de Graaff | |
| 574 | 574 | Tim Graham | |
| 575 | 575 | Kim Gräsman | |
| 576 | + Alex Grönholm | ||
| 576 | 577 | Nathaniel Gray | |
| 577 | 578 | Eddy De Greef | |
| 578 | 579 | Duane Griffin | |
@@ -594,6 +595,7 @@ Michael Guravage | |||
| 594 | 595 | Lars Gustäbel | |
| 595 | 596 | Thomas Güttler | |
| 596 | 597 | Jonas H. | |
| 598 | + Antti Haapala | ||
| 597 | 599 | Joseph Hackman | |
| 598 | 600 | Barry Haddow | |
| 599 | 601 | Philipp Hagemeister | |
| Back | FazBrowse Home | New Git URL |
0 commit comments