Skip to content

Commit 2b2520a

Browse files
asvetlovgvanrossum
andauthored
gh-128289: Add eager_tasks parameter to asyncio.run() and asyncio.Runner (#128293)
Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> * Update Doc/library/asyncio-runner.rst Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> --------- Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
1 parent e8e7ffd commit 2b2520a

5 files changed

Lines changed: 71 additions & 5 deletions

File tree

‎Doc/library/asyncio-runner.rst‎

Lines changed: 26 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

@@ -45,10 +45,18 @@ Running an asyncio Program
4545
and should ideally only be called once. It is recommended to use
4646
*loop_factory* to configure the event loop.
4747

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

55+
.. note::
56+
57+
Users concerned about performance are encouraged to experiment with
58+
``eager_tasks=True`` in their code.
59+
5260
Example::
5361

5462
async def main():
@@ -74,11 +82,15 @@ Running an asyncio Program
7482

7583
*coro* can be any awaitable object.
7684

85+
.. versionchanged:: 3.16
86+
87+
Added *eager_tasks* parameter.
88+
7789

7890
Runner context manager
7991
======================
8092

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

8395
A context manager that simplifies *multiple* async function calls in the same
8496
context.
@@ -95,6 +107,9 @@ Runner context manager
95107
current one. By default :func:`asyncio.new_event_loop` is used and set as
96108
current event loop with :func:`asyncio.set_event_loop` if *loop_factory* is ``None``.
97109

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

100115
async def main():
@@ -104,8 +119,17 @@ Runner context manager
104119
with asyncio.Runner() as runner:
105120
runner.run(main())
106121

122+
.. note::
123+
124+
Users concerned about performance are encouraged to experiment with
125+
``eager_tasks=True`` in their code.
126+
107127
.. versionadded:: 3.11
108128

129+
.. versionchanged:: 3.16
130+
131+
Added *eager_tasks* parameter.
132+
109133
.. method:: run(coro, *, context=None)
110134

111135
Execute *coro* in the embedded event loop.

‎Doc/whatsnew/3.16.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ asyncio
144144
socket file created for *path*.
145145
(Contributed by Sam Bull in :gh:`94984`.)
146146

147+
* Add *eager_tasks* parameter to :func:`asyncio.run` function and
148+
:class:`asyncio.Runner` class constructor.
149+
(Contributed by Andrew Svetlov in :gh:`128289`.)
150+
147151

148152
bdb
149153
---

‎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
@@ -47,10 +48,11 @@ class Runner:
4748

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

50-
def __init__(self, *, debug=None, loop_factory=None):
51+
def __init__(self, *, debug=None, loop_factory=None, eager_tasks=False):
5152
self._state = _State.CREATED
5253
self._debug = debug
5354
self._loop_factory = loop_factory
55+
self._eager_tasks = eager_tasks
5456
self._loop = None
5557
self._context = None
5658
self._interrupt_count = 0
@@ -154,6 +156,8 @@ def _lazy_init(self):
154156
self._loop = self._loop_factory()
155157
if self._debug is not None:
156158
self._loop.set_debug(self._debug)
159+
if self._eager_tasks:
160+
self._loop.set_task_factory(tasks.eager_task_factory)
157161
self._context = contextvars.copy_context()
158162
self._state = _State.INITIALIZED
159163

@@ -167,7 +171,7 @@ def _on_sigint(self, signum, frame, main_task):
167171
raise KeyboardInterrupt()
168172

169173

170-
def run(main, *, debug=None, loop_factory=None):
174+
def run(main, *, debug=None, loop_factory=None, eager_tasks=False):
171175
"""Execute the coroutine and return the result.
172176
173177
This function runs the passed coroutine, taking care of
@@ -179,6 +183,7 @@ def run(main, *, debug=None, loop_factory=None):
179183
180184
If debug is True, the event loop will be run in debug mode.
181185
If loop_factory is passed, it is used for new event loop creation.
186+
If eager_tasks is True, the loop creates eager tasks by default.
182187
183188
This function always creates a new event loop and closes it at the end.
184189
It should be used as a main entry point for asyncio programs, and should
@@ -201,7 +206,8 @@ async def main():
201206
raise RuntimeError(
202207
"asyncio.run() cannot be called from a running event loop")
203208

204-
with Runner(debug=debug, loop_factory=loop_factory) as runner:
209+
with Runner(debug=debug, loop_factory=loop_factory,
210+
eager_tasks=eager_tasks) as runner:
205211
return runner.run(main)
206212

207213

‎Lib/test/test_asyncio/test_runners.py‎

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

272272
asyncio.run(main(), loop_factory=asyncio.EventLoop)
273273

274+
def test_default_task_factory(self):
275+
async def main():
276+
factory = asyncio.get_running_loop().get_task_factory()
277+
self.assertIsNone(factory)
278+
279+
asyncio.run(main())
280+
281+
def test_eager_task_factory(self):
282+
async def main():
283+
factory = asyncio.get_running_loop().get_task_factory()
284+
self.assertIs(factory, asyncio.eager_task_factory)
285+
286+
asyncio.run(main(), eager_tasks=True)
287+
274288

275289
class RunnerTests(BaseTest):
276290

@@ -513,6 +527,22 @@ async def coro():
513527

514528
self.assertEqual(0, result.repr_count)
515529

530+
def test_default_task_factory(self):
531+
async def main():
532+
factory = asyncio.get_running_loop().get_task_factory()
533+
self.assertIsNone(factory)
534+
535+
with asyncio.Runner() as runner:
536+
runner.run(main())
537+
538+
def test_eager_task_factory(self):
539+
async def main():
540+
factory = asyncio.get_running_loop().get_task_factory()
541+
self.assertIs(factory, asyncio.eager_task_factory)
542+
543+
with asyncio.Runner(eager_tasks=True) as runner:
544+
runner.run(main())
545+
516546

517547
if __name__ == '__main__':
518548
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)