https://github.com/python/cpython/commit/2b2520abfac79e51dd45a06ec830703c84b49ac6
commit: 2b2520abfac79e51dd45a06ec830703c84b49ac6
branch: main
author: Andrew Svetlov <[email protected]>
committer: asvetlov <[email protected]>
date: 2026-10-29T10:56:03+01:00
summary:

gh-128289: Add eager_tasks parameter to asyncio.run() and asyncio.Runner 
(#128293)


Co-authored-by: Guido van Rossum <[email protected]>

* Update Doc/library/asyncio-runner.rst

Co-authored-by: Guido van Rossum <[email protected]>

---------

Co-authored-by: Guido van Rossum <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-12-27-15-41-55.gh-issue-128289.-hCzEc.rst
M Doc/library/asyncio-runner.rst
M Doc/whatsnew/3.16.rst
M Lib/asyncio/runners.py
M Lib/test/test_asyncio/test_runners.py

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index cd125b30703c79..cd42705bad83a8 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -22,7 +22,7 @@ to simplify async code usage for common wide-spread scenarios.
 Running an asyncio Program
 ==========================
 
-.. function:: run(coro, *, debug=None, loop_factory=None)
+.. function:: run(coro, *, debug=None, loop_factory=None, eager_tasks=False)
 
    Execute *coro* in an asyncio event loop and return the result.
 
@@ -45,10 +45,18 @@ Running an asyncio Program
    and should ideally only be called once. It is recommended to use
    *loop_factory* to configure the event loop.
 
+   If *eager_tasks* is ``True``, the created loop is configured to use
+   :ref:`eager-task-factory` by default.
+
    The executor is given a timeout duration of 5 minutes to shutdown.
    If the executor hasn't finished within that duration, a warning is
    emitted and the executor is closed.
 
+   .. note::
+
+      Users concerned about performance are encouraged to experiment with
+      ``eager_tasks=True`` in their code.
+
    Example::
 
        async def main():
@@ -74,11 +82,15 @@ Running an asyncio Program
 
       *coro* can be any awaitable object.
 
+   .. versionchanged:: 3.16
+
+      Added *eager_tasks* parameter.
+
 
 Runner context manager
 ======================
 
-.. class:: Runner(*, debug=None, loop_factory=None)
+.. class:: Runner(*, debug=None, loop_factory=None, eager_tasks=False)
 
    A context manager that simplifies *multiple* async function calls in the 
same
    context.
@@ -95,6 +107,9 @@ Runner context manager
    current one. By default :func:`asyncio.new_event_loop` is used and set as
    current event loop with :func:`asyncio.set_event_loop` if *loop_factory* is 
``None``.
 
+   If *eager_tasks* is ``True``, the created loop is configured to use
+   :ref:`eager-task-factory` by default.
+
    Basically, :func:`asyncio.run` example can be rewritten with the runner 
usage::
 
         async def main():
@@ -104,8 +119,17 @@ Runner context manager
         with asyncio.Runner() as runner:
             runner.run(main())
 
+   .. note::
+
+      Users concerned about performance are encouraged to experiment with
+      ``eager_tasks=True`` in their code.
+
    .. versionadded:: 3.11
 
+   .. versionchanged:: 3.16
+
+      Added *eager_tasks* parameter.
+
    .. method:: run(coro, *, context=None)
 
       Execute *coro* in the embedded event loop.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index e342aa71c7924e..ce001d07731bcc 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -144,6 +144,10 @@ asyncio
   socket file created for *path*.
   (Contributed by Sam Bull in :gh:`94984`.)
 
+* Add *eager_tasks* parameter to :func:`asyncio.run` function and
+  :class:`asyncio.Runner` class constructor.
+  (Contributed by Andrew Svetlov in :gh:`128289`.)
+
 
 bdb
 ---
diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 774a317564df22..deafd9a7006f6b 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -27,6 +27,7 @@ class Runner:
 
     If debug is True, the event loop will be run in debug mode.
     If loop_factory is passed, it is used for new event loop creation.
+    If eager_tasks is True, the loop creates eager tasks by default.
 
     asyncio.run(main(), debug=True)
 
@@ -47,10 +48,11 @@ class Runner:
 
     # Note: the class is final, it is not intended for inheritance.
 
-    def __init__(self, *, debug=None, loop_factory=None):
+    def __init__(self, *, debug=None, loop_factory=None, eager_tasks=False):
         self._state = _State.CREATED
         self._debug = debug
         self._loop_factory = loop_factory
+        self._eager_tasks = eager_tasks
         self._loop = None
         self._context = None
         self._interrupt_count = 0
@@ -154,6 +156,8 @@ def _lazy_init(self):
             self._loop = self._loop_factory()
         if self._debug is not None:
             self._loop.set_debug(self._debug)
+        if self._eager_tasks:
+            self._loop.set_task_factory(tasks.eager_task_factory)
         self._context = contextvars.copy_context()
         self._state = _State.INITIALIZED
 
@@ -167,7 +171,7 @@ def _on_sigint(self, signum, frame, main_task):
         raise KeyboardInterrupt()
 
 
-def run(main, *, debug=None, loop_factory=None):
+def run(main, *, debug=None, loop_factory=None, eager_tasks=False):
     """Execute the coroutine and return the result.
 
     This function runs the passed coroutine, taking care of
@@ -179,6 +183,7 @@ def run(main, *, debug=None, loop_factory=None):
 
     If debug is True, the event loop will be run in debug mode.
     If loop_factory is passed, it is used for new event loop creation.
+    If eager_tasks is True, the loop creates eager tasks by default.
 
     This function always creates a new event loop and closes it at the end.
     It should be used as a main entry point for asyncio programs, and should
@@ -201,7 +206,8 @@ async def main():
         raise RuntimeError(
             "asyncio.run() cannot be called from a running event loop")
 
-    with Runner(debug=debug, loop_factory=loop_factory) as runner:
+    with Runner(debug=debug, loop_factory=loop_factory,
+                eager_tasks=eager_tasks) as runner:
         return runner.run(main)
 
 
diff --git a/Lib/test/test_asyncio/test_runners.py 
b/Lib/test/test_asyncio/test_runners.py
index b9d94833a2cccd..c9370358aee953 100644
--- a/Lib/test/test_asyncio/test_runners.py
+++ b/Lib/test/test_asyncio/test_runners.py
@@ -271,6 +271,20 @@ async def main():
 
         asyncio.run(main(), loop_factory=asyncio.EventLoop)
 
+    def test_default_task_factory(self):
+        async def main():
+            factory = asyncio.get_running_loop().get_task_factory()
+            self.assertIsNone(factory)
+
+        asyncio.run(main())
+
+    def test_eager_task_factory(self):
+        async def main():
+            factory = asyncio.get_running_loop().get_task_factory()
+            self.assertIs(factory, asyncio.eager_task_factory)
+
+        asyncio.run(main(), eager_tasks=True)
+
 
 class RunnerTests(BaseTest):
 
@@ -513,6 +527,22 @@ async def coro():
 
         self.assertEqual(0, result.repr_count)
 
+    def test_default_task_factory(self):
+        async def main():
+            factory = asyncio.get_running_loop().get_task_factory()
+            self.assertIsNone(factory)
+
+        with asyncio.Runner() as runner:
+            runner.run(main())
+
+    def test_eager_task_factory(self):
+        async def main():
+            factory = asyncio.get_running_loop().get_task_factory()
+            self.assertIs(factory, asyncio.eager_task_factory)
+
+        with asyncio.Runner(eager_tasks=True) as runner:
+            runner.run(main())
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2024-12-27-15-41-55.gh-issue-128289.-hCzEc.rst 
b/Misc/NEWS.d/next/Library/2024-12-27-15-41-55.gh-issue-128289.-hCzEc.rst
new file mode 100644
index 00000000000000..015512afad6ed3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-27-15-41-55.gh-issue-128289.-hCzEc.rst
@@ -0,0 +1,2 @@
+Add *eager_tasks* parameter to :func:`asyncio.run` function and
+:class:`asyncio.Runner` class constructor.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to