• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. currentmodule:: asyncio
2
3.. _asyncio-dev:
4
5Develop with asyncio
6====================
7
8Asynchronous programming is different than classical "sequential" programming.
9This page lists common traps and explains how to avoid them.
10
11
12.. _asyncio-debug-mode:
13
14Debug mode of asyncio
15---------------------
16
17The implementation of :mod:`asyncio` has been written for performance.
18In order to ease the development of asynchronous code, you may wish to
19enable *debug mode*.
20
21To enable all debug checks for an application:
22
23* Enable the asyncio debug mode globally by setting the environment variable
24  :envvar:`PYTHONASYNCIODEBUG` to ``1``, or by calling :meth:`AbstractEventLoop.set_debug`.
25* Set the log level of the :ref:`asyncio logger <asyncio-logger>` to
26  :py:data:`logging.DEBUG`. For example, call
27  ``logging.basicConfig(level=logging.DEBUG)`` at startup.
28* Configure the :mod:`warnings` module to display :exc:`ResourceWarning`
29  warnings. For example, use the ``-Wdefault`` command line option of Python to
30  display them.
31
32Examples debug checks:
33
34* Log :ref:`coroutines defined but never "yielded from"
35  <asyncio-coroutine-not-scheduled>`
36* :meth:`~AbstractEventLoop.call_soon` and :meth:`~AbstractEventLoop.call_at` methods
37  raise an exception if they are called from the wrong thread.
38* Log the execution time of the selector
39* Log callbacks taking more than 100 ms to be executed. The
40  :attr:`AbstractEventLoop.slow_callback_duration` attribute is the minimum
41  duration in seconds of "slow" callbacks.
42* :exc:`ResourceWarning` warnings are emitted when transports and event loops
43  are :ref:`not closed explicitly <asyncio-close-transports>`.
44
45.. seealso::
46
47   The :meth:`AbstractEventLoop.set_debug` method and the :ref:`asyncio logger
48   <asyncio-logger>`.
49
50
51Cancellation
52------------
53
54Cancellation of tasks is not common in classic programming. In asynchronous
55programming, not only it is something common, but you have to prepare your
56code to handle it.
57
58Futures and tasks can be cancelled explicitly with their :meth:`Future.cancel`
59method. The :func:`wait_for` function cancels the waited task when the timeout
60occurs. There are many other cases where a task can be cancelled indirectly.
61
62Don't call :meth:`~Future.set_result` or :meth:`~Future.set_exception` method
63of :class:`Future` if the future is cancelled: it would fail with an exception.
64For example, write::
65
66    if not fut.cancelled():
67        fut.set_result('done')
68
69Don't schedule directly a call to the :meth:`~Future.set_result` or the
70:meth:`~Future.set_exception` method of a future with
71:meth:`AbstractEventLoop.call_soon`: the future can be cancelled before its method
72is called.
73
74If you wait for a future, you should check early if the future was cancelled to
75avoid useless operations. Example::
76
77    @coroutine
78    def slow_operation(fut):
79        if fut.cancelled():
80            return
81        # ... slow computation ...
82        yield from fut
83        # ...
84
85The :func:`shield` function can also be used to ignore cancellation.
86
87
88.. _asyncio-multithreading:
89
90Concurrency and multithreading
91------------------------------
92
93An event loop runs in a thread and executes all callbacks and tasks in the same
94thread. While a task is running in the event loop, no other task is running in
95the same thread. But when the task uses ``yield from``, the task is suspended
96and the event loop executes the next task.
97
98To schedule a callback from a different thread, the
99:meth:`AbstractEventLoop.call_soon_threadsafe` method should be used. Example::
100
101    loop.call_soon_threadsafe(callback, *args)
102
103Most asyncio objects are not thread safe. You should only worry if you access
104objects outside the event loop. For example, to cancel a future, don't call
105directly its :meth:`Future.cancel` method, but::
106
107    loop.call_soon_threadsafe(fut.cancel)
108
109To handle signals and to execute subprocesses, the event loop must be run in
110the main thread.
111
112To schedule a coroutine object from a different thread, the
113:func:`run_coroutine_threadsafe` function should be used. It returns a
114:class:`concurrent.futures.Future` to access the result::
115
116     future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
117     result = future.result(timeout)  # Wait for the result with a timeout
118
119The :meth:`AbstractEventLoop.run_in_executor` method can be used with a thread pool
120executor to execute a callback in different thread to not block the thread of
121the event loop.
122
123.. seealso::
124
125   The :ref:`Synchronization primitives <asyncio-sync>` section describes ways
126   to synchronize tasks.
127
128   The :ref:`Subprocess and threads <asyncio-subprocess-threads>` section lists
129   asyncio limitations to run subprocesses from different threads.
130
131
132
133
134.. _asyncio-handle-blocking:
135
136Handle blocking functions correctly
137-----------------------------------
138
139Blocking functions should not be called directly. For example, if a function
140blocks for 1 second, other tasks are delayed by 1 second which can have an
141important impact on reactivity.
142
143For networking and subprocesses, the :mod:`asyncio` module provides high-level
144APIs like :ref:`protocols <asyncio-protocol>`.
145
146An executor can be used to run a task in a different thread or even in a
147different process, to not block the thread of the event loop. See the
148:meth:`AbstractEventLoop.run_in_executor` method.
149
150.. seealso::
151
152   The :ref:`Delayed calls <asyncio-delayed-calls>` section details how the
153   event loop handles time.
154
155
156.. _asyncio-logger:
157
158Logging
159-------
160
161The :mod:`asyncio` module logs information with the :mod:`logging` module in
162the logger ``'asyncio'``.
163
164The default log level for the :mod:`asyncio` module is :py:data:`logging.INFO`.
165For those not wanting such verbosity from :mod:`asyncio` the log level can
166be changed.  For example, to change the level to :py:data:`logging.WARNING`:
167
168.. code-block:: none
169
170   logging.getLogger('asyncio').setLevel(logging.WARNING)
171
172
173.. _asyncio-coroutine-not-scheduled:
174
175Detect coroutine objects never scheduled
176----------------------------------------
177
178When a coroutine function is called and its result is not passed to
179:func:`ensure_future` or to the :meth:`AbstractEventLoop.create_task` method,
180the execution of the coroutine object will never be scheduled which is
181probably a bug.  :ref:`Enable the debug mode of asyncio <asyncio-debug-mode>`
182to :ref:`log a warning <asyncio-logger>` to detect it.
183
184Example with the bug::
185
186    import asyncio
187
188    @asyncio.coroutine
189    def test():
190        print("never scheduled")
191
192    test()
193
194Output in debug mode::
195
196    Coroutine test() at test.py:3 was never yielded from
197    Coroutine object created at (most recent call last):
198      File "test.py", line 7, in <module>
199        test()
200
201The fix is to call the :func:`ensure_future` function or the
202:meth:`AbstractEventLoop.create_task` method with the coroutine object.
203
204.. seealso::
205
206   :ref:`Pending task destroyed <asyncio-pending-task-destroyed>`.
207
208
209Detect exceptions never consumed
210--------------------------------
211
212Python usually calls :func:`sys.displayhook` on unhandled exceptions. If
213:meth:`Future.set_exception` is called, but the exception is never consumed,
214:func:`sys.displayhook` is not called. Instead, :ref:`a log is emitted
215<asyncio-logger>` when the future is deleted by the garbage collector, with the
216traceback where the exception was raised.
217
218Example of unhandled exception::
219
220    import asyncio
221
222    @asyncio.coroutine
223    def bug():
224        raise Exception("not consumed")
225
226    loop = asyncio.get_event_loop()
227    asyncio.ensure_future(bug())
228    loop.run_forever()
229    loop.close()
230
231Output::
232
233    Task exception was never retrieved
234    future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)>
235    Traceback (most recent call last):
236      File "asyncio/tasks.py", line 237, in _step
237        result = next(coro)
238      File "asyncio/coroutines.py", line 141, in coro
239        res = func(*args, **kw)
240      File "test.py", line 5, in bug
241        raise Exception("not consumed")
242    Exception: not consumed
243
244:ref:`Enable the debug mode of asyncio <asyncio-debug-mode>` to get the
245traceback where the task was created. Output in debug mode::
246
247    Task exception was never retrieved
248    future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8>
249    source_traceback: Object created at (most recent call last):
250      File "test.py", line 8, in <module>
251        asyncio.ensure_future(bug())
252    Traceback (most recent call last):
253      File "asyncio/tasks.py", line 237, in _step
254        result = next(coro)
255      File "asyncio/coroutines.py", line 79, in __next__
256        return next(self.gen)
257      File "asyncio/coroutines.py", line 141, in coro
258        res = func(*args, **kw)
259      File "test.py", line 5, in bug
260        raise Exception("not consumed")
261    Exception: not consumed
262
263There are different options to fix this issue. The first option is to chain the
264coroutine in another coroutine and use classic try/except::
265
266    @asyncio.coroutine
267    def handle_exception():
268        try:
269            yield from bug()
270        except Exception:
271            print("exception consumed")
272
273    loop = asyncio.get_event_loop()
274    asyncio.ensure_future(handle_exception())
275    loop.run_forever()
276    loop.close()
277
278Another option is to use the :meth:`AbstractEventLoop.run_until_complete`
279function::
280
281    task = asyncio.ensure_future(bug())
282    try:
283        loop.run_until_complete(task)
284    except Exception:
285        print("exception consumed")
286
287.. seealso::
288
289   The :meth:`Future.exception` method.
290
291
292Chain coroutines correctly
293--------------------------
294
295When a coroutine function calls other coroutine functions and tasks, they
296should be chained explicitly with ``yield from``. Otherwise, the execution is
297not guaranteed to be sequential.
298
299Example with different bugs using :func:`asyncio.sleep` to simulate slow
300operations::
301
302    import asyncio
303
304    @asyncio.coroutine
305    def create():
306        yield from asyncio.sleep(3.0)
307        print("(1) create file")
308
309    @asyncio.coroutine
310    def write():
311        yield from asyncio.sleep(1.0)
312        print("(2) write into file")
313
314    @asyncio.coroutine
315    def close():
316        print("(3) close file")
317
318    @asyncio.coroutine
319    def test():
320        asyncio.ensure_future(create())
321        asyncio.ensure_future(write())
322        asyncio.ensure_future(close())
323        yield from asyncio.sleep(2.0)
324        loop.stop()
325
326    loop = asyncio.get_event_loop()
327    asyncio.ensure_future(test())
328    loop.run_forever()
329    print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))
330    loop.close()
331
332Expected output:
333
334.. code-block:: none
335
336    (1) create file
337    (2) write into file
338    (3) close file
339    Pending tasks at exit: set()
340
341Actual output:
342
343.. code-block:: none
344
345    (3) close file
346    (2) write into file
347    Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>}
348    Task was destroyed but it is pending!
349    task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>
350
351The loop stopped before the ``create()`` finished, ``close()`` has been called
352before ``write()``, whereas coroutine functions were called in this order:
353``create()``, ``write()``, ``close()``.
354
355To fix the example, tasks must be marked with ``yield from``::
356
357    @asyncio.coroutine
358    def test():
359        yield from asyncio.ensure_future(create())
360        yield from asyncio.ensure_future(write())
361        yield from asyncio.ensure_future(close())
362        yield from asyncio.sleep(2.0)
363        loop.stop()
364
365Or without ``asyncio.ensure_future()``::
366
367    @asyncio.coroutine
368    def test():
369        yield from create()
370        yield from write()
371        yield from close()
372        yield from asyncio.sleep(2.0)
373        loop.stop()
374
375
376.. _asyncio-pending-task-destroyed:
377
378Pending task destroyed
379----------------------
380
381If a pending task is destroyed, the execution of its wrapped :ref:`coroutine
382<coroutine>` did not complete. It is probably a bug and so a warning is logged.
383
384Example of log:
385
386.. code-block:: none
387
388    Task was destroyed but it is pending!
389    task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>
390
391:ref:`Enable the debug mode of asyncio <asyncio-debug-mode>` to get the
392traceback where the task was created. Example of log in debug mode:
393
394.. code-block:: none
395
396    Task was destroyed but it is pending!
397    source_traceback: Object created at (most recent call last):
398      File "test.py", line 15, in <module>
399        task = asyncio.ensure_future(coro, loop=loop)
400    task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()] created at test.py:7> created at test.py:15>
401
402
403.. seealso::
404
405   :ref:`Detect coroutine objects never scheduled <asyncio-coroutine-not-scheduled>`.
406
407.. _asyncio-close-transports:
408
409Close transports and event loops
410--------------------------------
411
412When a transport is no more needed, call its ``close()`` method to release
413resources. Event loops must also be closed explicitly.
414
415If a transport or an event loop is not closed explicitly, a
416:exc:`ResourceWarning` warning will be emitted in its destructor. By default,
417:exc:`ResourceWarning` warnings are ignored. The :ref:`Debug mode of asyncio
418<asyncio-debug-mode>` section explains how to display them.
419