summaryrefslogtreecommitdiffstats
path: root/Doc/library/asyncio-runner.rst
blob: d0df1db892f9ec7101b4d1fd7350bd5171cf1803 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
.. currentmodule:: asyncio


=======
Runners
=======

**Source code:** :source:`Lib/asyncio/runners.py`


This section outlines high-level asyncio primitives to run asyncio code.

They are built on top of an :ref:`event loop <asyncio-event-loop>` with the aim
to simplify async code usage for common wide-spread scenarios.

.. contents::
   :depth: 1
   :local:



Running an asyncio Program
==========================

.. function:: run(coro, *, debug=None)

   Execute the :term:`coroutine` *coro* and return the result.

   This function runs the passed coroutine, taking care of
   managing the asyncio event loop, *finalizing asynchronous
   generators*, and closing the threadpool.

   This function cannot be called when another asyncio event loop is
   running in the same thread.

   If *debug* is ``True``, the event loop will be run in debug mode. ``False`` disables
   debug mode explicitly. ``None`` is used to respect the global
   :ref:`asyncio-debug-mode` settings.

   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 ideally only be called once.

   Example::

       async def main():
           await asyncio.sleep(1)
           print('hello')

       asyncio.run(main())

   .. versionadded:: 3.7

   .. versionchanged:: 3.9
      Updated to use :meth:`loop.shutdown_default_executor`.

   .. versionchanged:: 3.10

      *debug* is ``None`` by default to respect the global debug mode settings.


Runner context manager
======================

.. class:: Runner(*, debug=None, loop_factory=None)

   A context manager that simplifies *multiple* async function calls in the same
   context.

   Sometimes several top-level async functions should be called in the same :ref:`event
   loop <asyncio-event-loop>` and :class:`contextvars.Context`.

   If *debug* is ``True``, the event loop will be run in debug mode. ``False`` disables
   debug mode explicitly. ``None`` is used to respect the global
   :ref:`asyncio-debug-mode` settings.

   *loop_factory* could be used for overriding the loop creation.
   :func:`asyncio.new_event_loop` is used if ``None``.

   Basically, :func:`asyncio.run()` example can be rewritten with the runner usage::

        async def main():
            await asyncio.sleep(1)
            print('hello')

        with asyncio.Runner() as runner:
            runner.run(main())

   .. versionadded:: 3.11

   .. method:: run(coro, *, context=None)

      Run a :term:`coroutine <coroutine>` *coro* in the embedded loop.

      Return the coroutine's result or raise its exception.

      An optional keyword-only *context* argument allows specifying a
      custom :class:`contextvars.Context` for the *coro* to run in.
      The runner's default context is used if ``None``.

      This function cannot be called when another asyncio event loop is
      running in the same thread.

   .. method:: close()

      Close the runner.

      Finalize asynchronous generators, shutdown default executor, close the event loop
      and release embedded :class:`contextvars.Context`.

   .. method:: get_loop()

      Return the event loop associated with the runner instance.

   .. note::

      :class:`Runner` uses the lazy initialization strategy, its constructor doesn't
      initialize underlying low-level structures.

      Embedded *loop* and *context* are created at the :keyword:`with` body entering
      or the first call of :meth:`run` or :meth:`get_loop`.


Handling Keyboard Interruption
==============================

.. versionadded:: 3.11

When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, :exc:`KeyboardInterrupt`
exception is raised in the main thread by default. However this doesn't work with
:mod:`asyncio` because it can interrupt asyncio internals and can hang the program from
exiting.

To mitigate this issue, :mod:`asyncio` handles :const:`signal.SIGINT` as follows:

1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
   any user code is executed and removes it when exiting from the function.
2. The :class:`~asyncio.Runner` creates the main task for the passed coroutine for its
   execution.
3. When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, the custom signal handler
   cancels the main task by calling :meth:`asyncio.Task.cancel` which raises
   :exc:`asyncio.CancelledError` inside the main task.  This causes the Python stack
   to unwind, ``try/except`` and ``try/finally`` blocks can be used for resource
   cleanup.  After the main task is cancelled, :meth:`asyncio.Runner.run` raises
   :exc:`KeyboardInterrupt`.
4. A user could write a tight loop which cannot be interrupted by
   :meth:`asyncio.Task.cancel`, in which case the second following :kbd:`Ctrl-C`
   immediately raises the :exc:`KeyboardInterrupt` without cancelling the main task.