Skip to content

Commit 8493e59

Browse files
committed
Add eager_tasks parameter to asyncio.run() and asyncio.Runner
1 parent 0b5f1fa commit 8493e59

5 files changed

Lines changed: 72 additions & 6 deletions

File tree

‎Doc/library/asyncio-runner.rst‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ to simplify async code usage for common wide-spread scenarios.
2222
Running an asyncio Program
2323
==========================
2424

25-
.. function:: run(coro, *, debug=None, loop_factory=None)
25+
.. function:: run(coro, *, debug=None, loop_factory=None, eager_tasks=False)
2626

2727
Execute *coro* in an asyncio event loop and return the result.
2828

@@ -47,10 +47,18 @@ Running an asyncio Program
4747
Passing :class:`asyncio.EventLoop` allows running asyncio without the
4848
policy system.
4949

50+
If *eager_tasks* is ``True``, the created loop is configured to use
51+
:ref:`eager-task-factory` by default.
52+
5053
The executor is given a timeout duration of 5 minutes to shutdown.
5154
If the executor hasn't finished within that duration, a warning is
5255
emitted and the executor is closed.
5356

57+
.. note::
58+
59+
Users are encouraged to use ``eager_tasks=True`` in their code;
60+
the default lazy tasks will be deprecated starting from Python 3.16.
61+
5462
Example::
5563

5664
async def main():
@@ -76,11 +84,13 @@ Running an asyncio Program
7684

7785
*coro* can be any awaitable object.
7886

87+
Added *eager_tasks* parameter.
88+
7989

8090
Runner context manager
8191
======================
8292

83-
.. class:: Runner(*, debug=None, loop_factory=None)
93+
.. class:: Runner(*, debug=None, loop_factory=None, eager_tasks=False)
8494

8595
A context manager that simplifies *multiple* async function calls in the same
8696
context.
@@ -97,6 +107,9 @@ Runner context manager
97107
current one. By default :func:`asyncio.new_event_loop` is used and set as
98108
current event loop with :func:`asyncio.set_event_loop` if *loop_factory* is ``None``.
99109

110+
If *eager_tasks* is ``True``, the created loop is configured to use
111+
:ref:`eager-task-factory` by default.
112+
100113
Basically, :func:`asyncio.run` example can be rewritten with the runner usage::
101114

102115
async def main():
@@ -106,8 +119,17 @@ Runner context manager
106119
with asyncio.Runner() as runner:
107120
runner.run(main())
108121

122+
.. note::
123+
124+
Users are encouraged to use ``eager_tasks=True`` in their code;
125+
the default lazy tasks will be deprecated starting from Python 3.16.
126+
109127
.. versionadded:: 3.11
110128

129+
.. versionchanged:: 3.14
130+
131+
Added *eager_tasks* parameter.
132+
111133
.. method:: run(coro, *, context=None)
112134

113135
Execute *coro* in the embedded event loop.

‎Doc/whatsnew/3.14.rst‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
****************************
32
What's new in Python 3.14
43
****************************
@@ -292,6 +291,13 @@ ast
292291
* The ``repr()`` output for AST nodes now includes more information.
293292
(Contributed by Tomas R in :gh:`116022`.)
294293

294+
asyncio
295+
-------
296+
297+
* Add *eager_tasks* parameter to :func:`asyncio.run` function and
298+
:class:`asyncio.Runner` class constructor.
299+
(Contributed by Andrew Svetlov in :gh:`128289`.)
300+
295301
concurrent.futures
296302
------------------
297303

‎Lib/asyncio/runners.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Runner:
2727
2828
If debug is True, the event loop will be run in debug mode.
2929
If loop_factory is passed, it is used for new event loop creation.
30+
If eager_tasks is True, the loop creates eager tasks by default.
3031
3132
asyncio.run(main(), debug=True)
3233
@@ -46,10 +47,11 @@ class Runner:
4647

4748
# Note: the class is final, it is not intended for inheritance.
4849

49-
def __init__(self, *, debug=None, loop_factory=None):
50+
def __init__(self, *, debug=None, loop_factory=None, eager_tasks=False):
5051
self._state = _State.CREATED
5152
self._debug = debug
5253
self._loop_factory = loop_factory
54+
self._eager_tasks = eager_tasks
5355
self._loop = None
5456
self._context = None
5557
self._interrupt_count = 0
@@ -153,6 +155,8 @@ def _lazy_init(self):
153155
self._loop = self._loop_factory()
154156
if self._debug is not None:
155157
self._loop.set_debug(self._debug)
158+
if self._eager_tasks:
159+
self._loop.set_task_factory(tasks.eager_task_factory)
156160
self._context = contextvars.copy_context()
157161
self._state = _State.INITIALIZED
158162

@@ -166,7 +170,7 @@ def _on_sigint(self, signum, frame, main_task):
166170
raise KeyboardInterrupt()
167171

168172

169-
def run(main, *, debug=None, loop_factory=None):
173+
def run(main, *, debug=None, loop_factory=None, eager_tasks=False):
170174
"""Execute the coroutine and return the result.
171175
172176
This function runs the passed coroutine, taking care of
@@ -178,6 +182,7 @@ def run(main, *, debug=None, loop_factory=None):
178182
179183
If debug is True, the event loop will be run in debug mode.
180184
If loop_factory is passed, it is used for new event loop creation.
185+
If eager_tasks is True, the loop creates eager tasks by default.
181186
182187
This function always creates a new event loop and closes it at the end.
183188
It should be used as a main entry point for asyncio programs, and should
@@ -200,7 +205,8 @@ async def main():
200205
raise RuntimeError(
201206
"asyncio.run() cannot be called from a running event loop")
202207

203-
with Runner(debug=debug, loop_factory=loop_factory) as runner:
208+
with Runner(debug=debug, loop_factory=loop_factory,
209+
eager_tasks=eager_tasks) as runner:
204210
return runner.run(main)
205211

206212

‎Lib/test/test_asyncio/test_runners.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,20 @@ async def main():
283283

284284
asyncio.run(main(), loop_factory=asyncio.EventLoop)
285285

286+
def test_default_task_factory(self):
287+
async def main():
288+
factory = asyncio.get_running_loop().get_task_factory()
289+
self.assertIsNone(factory)
290+
291+
asyncio.run(main())
292+
293+
def test_eager_task_factory(self):
294+
async def main():
295+
factory = asyncio.get_running_loop().get_task_factory()
296+
self.assertIs(factory, asyncio.eager_task_factory)
297+
298+
asyncio.run(main(), eager_tasks=True)
299+
286300

287301
class RunnerTests(BaseTest):
288302

@@ -522,6 +536,22 @@ async def coro():
522536

523537
self.assertEqual(0, result.repr_count)
524538

539+
def test_default_task_factory(self):
540+
async def main():
541+
factory = asyncio.get_running_loop().get_task_factory()
542+
self.assertIsNone(factory)
543+
544+
with asyncio.Runner() as runner:
545+
runner.run(main())
546+
547+
def test_eager_task_factory(self):
548+
async def main():
549+
factory = asyncio.get_running_loop().get_task_factory()
550+
self.assertIs(factory, asyncio.eager_task_factory)
551+
552+
with asyncio.Runner(eager_tasks=True) as runner:
553+
runner.run(main())
554+
525555

526556
if __name__ == '__main__':
527557
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add *eager_tasks* parameter to :func:`asyncio.run` function and
2+
:class:`asyncio.Runner` class constructor.

0 commit comments

Comments
 (0)