Home | History | Annotate | Download | only in library
      1 .. currentmodule:: asyncio
      2 
      3 .. _asyncio-dev:
      4 
      5 =======================
      6 Developing with asyncio
      7 =======================
      8 
      9 Asynchronous programming is different from classic "sequential"
     10 programming.
     11 
     12 This page lists common mistakes and traps and explains how
     13 to avoid them.
     14 
     15 
     16 .. _asyncio-debug-mode:
     17 
     18 Debug Mode
     19 ==========
     20 
     21 By default asyncio runs in production mode.  In order to ease
     22 the development asyncio has a *debug mode*.
     23 
     24 There are several ways to enable asyncio debug mode:
     25 
     26 * Setting the :envvar:`PYTHONASYNCIODEBUG` environment variable to ``1``.
     27 
     28 * Using the :option:`-X` ``dev`` Python command line option.
     29 
     30 * Passing ``debug=True`` to :func:`asyncio.run`.
     31 
     32 * Calling :meth:`loop.set_debug`.
     33 
     34 In addition to enabling the debug mode, consider also:
     35 
     36 * setting the log level of the :ref:`asyncio logger <asyncio-logger>` to
     37   :py:data:`logging.DEBUG`, for example the following snippet of code
     38   can be run at startup of the application::
     39 
     40     logging.basicConfig(level=logging.DEBUG)
     41 
     42 * configuring the :mod:`warnings` module to display
     43   :exc:`ResourceWarning` warnings.  One way of doing that is by
     44   using the :option:`-W` ``default`` command line option.
     45 
     46 
     47 When the debug mode is enabled:
     48 
     49 * asyncio checks for :ref:`coroutines that were not awaited
     50   <asyncio-coroutine-not-scheduled>` and logs them; this mitigates
     51   the "forgotten await" pitfall.
     52 
     53 * Many non-threadsafe asyncio APIs (such as :meth:`loop.call_soon` and
     54   :meth:`loop.call_at` methods) raise an exception if they are called
     55   from a wrong thread.
     56 
     57 * The execution time of the I/O selector is logged if it takes too long to
     58   perform an I/O operation.
     59 
     60 * Callbacks taking longer than 100ms are logged.  The
     61   :attr:`loop.slow_callback_duration` attribute can be used to set the
     62   minimum execution duration in seconds that is considered "slow".
     63 
     64 
     65 .. _asyncio-multithreading:
     66 
     67 Concurrency and Multithreading
     68 ==============================
     69 
     70 An event loop runs in a thread (typically the main thread) and executes
     71 all callbacks and Tasks in its thread.  While a Task is running in the
     72 event loop, no other Tasks can run in the same thread.  When a Task
     73 executes an ``await`` expression, the running Task gets suspended, and
     74 the event loop executes the next Task.
     75 
     76 To schedule a callback from a different OS thread, the
     77 :meth:`loop.call_soon_threadsafe` method should be used. Example::
     78 
     79     loop.call_soon_threadsafe(callback, *args)
     80 
     81 Almost all asyncio objects are not thread safe, which is typically
     82 not a problem unless there is code that works with them from outside
     83 of a Task or a callback.  If there's a need for such code to call a
     84 low-level asyncio API, the :meth:`loop.call_soon_threadsafe` method
     85 should be used, e.g.::
     86 
     87     loop.call_soon_threadsafe(fut.cancel)
     88 
     89 To schedule a coroutine object from a different OS thread, the
     90 :func:`run_coroutine_threadsafe` function should be used. It returns a
     91 :class:`concurrent.futures.Future` to access the result::
     92 
     93      async def coro_func():
     94           return await asyncio.sleep(1, 42)
     95 
     96      # Later in another OS thread:
     97 
     98      future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
     99      # Wait for the result:
    100      result = future.result()
    101 
    102 To handle signals and to execute subprocesses, the event loop must be
    103 run in the main thread.
    104 
    105 The :meth:`loop.run_in_executor` method can be used with a
    106 :class:`concurrent.futures.ThreadPoolExecutor` to execute
    107 blocking code in a different OS thread without blocking the OS thread
    108 that the event loop runs in.
    109 
    110 
    111 .. _asyncio-handle-blocking:
    112 
    113 Running Blocking Code
    114 =====================
    115 
    116 Blocking (CPU-bound) code should not be called directly.  For example,
    117 if a function performs a CPU-intensive calculation for 1 second,
    118 all concurrent asyncio Tasks and IO operations would be delayed
    119 by 1 second.
    120 
    121 An executor can be used to run a task in a different thread or even in
    122 a different process to avoid blocking block the OS thread with the
    123 event loop.  See the :meth:`loop.run_in_executor` method for more
    124 details.
    125 
    126 
    127 .. _asyncio-logger:
    128 
    129 Logging
    130 =======
    131 
    132 asyncio uses the :mod:`logging` module and all logging is performed
    133 via the ``"asyncio"`` logger.
    134 
    135 The default log level is :py:data:`logging.INFO`, which can be easily
    136 adjusted::
    137 
    138    logging.getLogger("asyncio").setLevel(logging.WARNING)
    139 
    140 
    141 .. _asyncio-coroutine-not-scheduled:
    142 
    143 Detect never-awaited coroutines
    144 ===============================
    145 
    146 When a coroutine function is called, but not awaited
    147 (e.g. ``coro()`` instead of ``await coro()``)
    148 or the coroutine is not scheduled with :meth:`asyncio.create_task`, asyncio
    149 will emit a :exc:`RuntimeWarning`::
    150 
    151     import asyncio
    152 
    153     async def test():
    154         print("never scheduled")
    155 
    156     async def main():
    157         test()
    158 
    159     asyncio.run(main())
    160 
    161 Output::
    162 
    163   test.py:7: RuntimeWarning: coroutine 'test' was never awaited
    164     test()
    165 
    166 Output in debug mode::
    167 
    168   test.py:7: RuntimeWarning: coroutine 'test' was never awaited
    169   Coroutine created at (most recent call last)
    170     File "../t.py", line 9, in <module>
    171       asyncio.run(main(), debug=True)
    172 
    173     < .. >
    174 
    175     File "../t.py", line 7, in main
    176       test()
    177     test()
    178 
    179 The usual fix is to either await the coroutine or call the
    180 :meth:`asyncio.create_task` function::
    181 
    182     async def main():
    183         await test()
    184 
    185 
    186 Detect never-retrieved exceptions
    187 =================================
    188 
    189 If a :meth:`Future.set_exception` is called but the Future object is
    190 never awaited on, the exception would never be propagated to the
    191 user code.  In this case, asyncio would emit a log message when the
    192 Future object is garbage collected.
    193 
    194 Example of an unhandled exception::
    195 
    196     import asyncio
    197 
    198     async def bug():
    199         raise Exception("not consumed")
    200 
    201     async def main():
    202         asyncio.create_task(bug())
    203 
    204     asyncio.run(main())
    205 
    206 Output::
    207 
    208     Task exception was never retrieved
    209     future: <Task finished coro=<bug() done, defined at test.py:3>
    210       exception=Exception('not consumed')>
    211 
    212     Traceback (most recent call last):
    213       File "test.py", line 4, in bug
    214         raise Exception("not consumed")
    215     Exception: not consumed
    216 
    217 :ref:`Enable the debug mode <asyncio-debug-mode>` to get the
    218 traceback where the task was created::
    219 
    220     asyncio.run(main(), debug=True)
    221 
    222 Output in debug mode::
    223 
    224     Task exception was never retrieved
    225     future: <Task finished coro=<bug() done, defined at test.py:3>
    226         exception=Exception('not consumed') created at asyncio/tasks.py:321>
    227 
    228     source_traceback: Object created at (most recent call last):
    229       File "../t.py", line 9, in <module>
    230         asyncio.run(main(), debug=True)
    231 
    232     < .. >
    233 
    234     Traceback (most recent call last):
    235       File "../t.py", line 4, in bug
    236         raise Exception("not consumed")
    237     Exception: not consumed
    238