Skip to content

Commit 4715039

Browse files
1st1elprans
andauthored
bpo-33649: Add a high-level section about Futures; few quick fixes (GH-9403)
Co-authored-by: Elvis Pranskevichus <[email protected]>
1 parent a3c88ef commit 4715039

File tree

5 files changed

+148
-45
lines changed

5 files changed

+148
-45
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ Availability: Unix.
989989
Executing code in thread or process pools
990990
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
991991

992-
.. coroutinemethod:: loop.run_in_executor(executor, func, \*args)
992+
.. awaitablemethod:: loop.run_in_executor(executor, func, \*args)
993993

994994
Arrange for *func* to be called in the specified executor.
995995

Doc/library/asyncio-future.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Futures
88
=======
99

10-
*Future* objects are used to bridge low-level callback-based code
10+
*Future* objects are used to bridge **low-level callback-based code**
1111
with high-level async/await code.
1212

1313

Doc/library/asyncio-task.rst

Lines changed: 124 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,31 @@ To actually run a coroutine asyncio provides three main mechanisms:
103103
world
104104
finished at 17:14:34
105105

106+
107+
.. _asyncio-awaitables:
108+
109+
Awaitables
110+
==========
111+
112+
We say that an object is an *awaitable* object if it can be used
113+
in an :keyword:`await` expression.
114+
115+
116+
.. rubric:: Coroutines and Tasks
117+
118+
Python coroutines are *awaitables*::
119+
120+
async def nested():
121+
return 42
122+
123+
async def main():
124+
# Will print "42":
125+
print(await nested())
126+
127+
*Tasks* are used to schedule coroutines *concurrently*.
128+
See the previous :ref:`section <coroutine>` for an introduction
129+
to coroutines and tasks.
130+
106131
Note that in this documentation the term "coroutine" can be used for
107132
two closely related concepts:
108133

@@ -112,14 +137,41 @@ two closely related concepts:
112137
*coroutine function*.
113138

114139

140+
.. rubric:: Futures
141+
142+
There is a dedicated section about the :ref:`asyncio Future object
143+
<asyncio-futures>`, but the concept is fundamental to asyncio so
144+
it needs a brief introduction in this section.
145+
146+
A Future is a special **low-level** awaitable object that represents
147+
an **eventual result** of an asynchronous operation.
148+
Future objects in asyncio are needed to allow callback-based code
149+
to be used with async/await.
150+
151+
Normally, **there is no need** to create Future objects at the
152+
application level code.
153+
154+
Future objects, sometimes exposed by libraries and some asyncio
155+
APIs, should be awaited::
156+
157+
async def main():
158+
await function_that_returns_a_future_object()
159+
160+
# this is also valid:
161+
await asyncio.gather(
162+
function_that_returns_a_future_object(),
163+
some_python_coroutine()
164+
)
165+
166+
115167
Running an asyncio Program
116168
==========================
117169

118170
.. function:: run(coro, \*, debug=False)
119171

120172
This function runs the passed coroutine, taking care of
121-
managing the asyncio event loop and finalizing asynchronous
122-
generators.
173+
managing the asyncio event loop and *finalizing asynchronous
174+
generators*.
123175

124176
This function cannot be called when another asyncio event loop is
125177
running in the same thread.
@@ -140,8 +192,8 @@ Creating Tasks
140192

141193
.. function:: create_task(coro, \*, name=None)
142194

143-
Wrap the *coro* :ref:`coroutine <coroutine>` into a task and schedule
144-
its execution. Return the task object.
195+
Wrap the *coro* :ref:`coroutine <coroutine>` into a Task and
196+
schedule its execution. Return the Task object.
145197

146198
If *name* is not ``None``, it is set as the name of the task using
147199
:meth:`Task.set_name`.
@@ -150,6 +202,21 @@ Creating Tasks
150202
:exc:`RuntimeError` is raised if there is no running loop in
151203
current thread.
152204

205+
This function has been **added in Python 3.7**. Prior to
206+
Python 3.7, the low-level :func:`asyncio.ensure_future` function
207+
can be used instead::
208+
209+
async def coro():
210+
...
211+
212+
# In Python 3.7+
213+
task = asyncio.create_task(coro())
214+
...
215+
216+
# This works in all Python versions but is less readable
217+
task = asyncio.ensure_future(coro())
218+
...
219+
153220
.. versionadded:: 3.7
154221

155222
.. versionchanged:: 3.8
@@ -166,6 +233,9 @@ Sleeping
166233
If *result* is provided, it is returned to the caller
167234
when the coroutine completes.
168235

236+
The *loop* argument is deprecated and scheduled for removal
237+
in Python 4.0.
238+
169239
.. _asyncio_example_sleep:
170240

171241
Example of coroutine displaying the current date every second
@@ -189,36 +259,31 @@ Sleeping
189259
Running Tasks Concurrently
190260
==========================
191261

192-
.. function:: gather(\*fs, loop=None, return_exceptions=False)
262+
.. awaitablefunction:: gather(\*fs, loop=None, return_exceptions=False)
193263

194-
Return a Future aggregating results from the given coroutine objects,
195-
Tasks, or Futures.
264+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
265+
sequence *concurrently*.
196266

197-
If all Tasks/Futures are completed successfully, the result is an
198-
aggregate list of returned values. The result values are in the
199-
order of the original *fs* sequence.
267+
If any awaitable in *fs* is a coroutine, it is automatically
268+
scheduled as a Task.
200269

201-
All coroutines in the *fs* list are automatically
202-
scheduled as :class:`Tasks <Task>`.
270+
If all awaitables are completed successfully, the result is an
271+
aggregate list of returned values. The order of result values
272+
corresponds to the order of awaitables in *fs*.
203273

204-
If *return_exceptions* is ``True``, exceptions in the Tasks/Futures
205-
are treated the same as successful results, and gathered in the
206-
result list. Otherwise, the first raised exception is immediately
207-
propagated to the returned Future.
274+
If *return_exceptions* is ``True``, exceptions are treated the
275+
same as successful results, and aggregated in the result list.
276+
Otherwise, the first raised exception is immediately propagated
277+
to the task that awaits on ``gather()``.
208278

209-
If the outer Future is *cancelled*, all submitted Tasks/Futures
279+
If ``gather`` is *cancelled*, all submitted awaitables
210280
(that have not completed yet) are also *cancelled*.
211281

212-
If any child is *cancelled*, it is treated as if it raised
213-
:exc:`CancelledError` -- the outer Future is **not** cancelled in
214-
this case. This is to prevent the cancellation of one submitted
215-
Task/Future to cause other Tasks/Futures to be cancelled.
216-
217-
All futures must share the same event loop.
218-
219-
.. versionchanged:: 3.7
220-
If the *gather* itself is cancelled, the cancellation is
221-
propagated regardless of *return_exceptions*.
282+
If any Task or Future from the *fs* sequence is *cancelled*, it is
283+
treated as if it raised :exc:`CancelledError` -- the ``gather()``
284+
call is **not** cancelled in this case. This is to prevent the
285+
cancellation of one submitted Task/Future to cause other
286+
Tasks/Futures to be cancelled.
222287

223288
.. _asyncio_example_gather:
224289

@@ -235,6 +300,7 @@ Running Tasks Concurrently
235300
print(f"Task {name}: factorial({number}) = {f}")
236301

237302
async def main():
303+
# Schedule three calls *concurrently*:
238304
await asyncio.gather(
239305
factorial("A", 2),
240306
factorial("B", 3),
@@ -255,17 +321,21 @@ Running Tasks Concurrently
255321
# Task C: Compute factorial(4)...
256322
# Task C: factorial(4) = 24
257323

324+
.. versionchanged:: 3.7
325+
If the *gather* itself is cancelled, the cancellation is
326+
propagated regardless of *return_exceptions*.
327+
258328

259329
Shielding Tasks From Cancellation
260330
=================================
261331

262-
.. coroutinefunction:: shield(fut, \*, loop=None)
332+
.. awaitablefunction:: shield(fut, \*, loop=None)
263333

264-
Wait for a Future/Task while protecting it from being cancelled.
334+
Protect an :ref:`awaitable object <asyncio-awaitables>`
335+
from being :meth:`cancelled <Task.cancel>`.
265336

266337
*fut* can be a coroutine, a Task, or a Future-like object. If
267-
*fut* is a coroutine it is automatically scheduled as a
268-
:class:`Task`.
338+
*fut* is a coroutine it is automatically scheduled as a Task.
269339

270340
The statement::
271341

@@ -299,11 +369,10 @@ Timeouts
299369

300370
.. coroutinefunction:: wait_for(fut, timeout, \*, loop=None)
301371

302-
Wait for a coroutine, Task, or Future to complete with timeout.
372+
Wait for the *fut* :ref:`awaitable <asyncio-awaitables>`
373+
to complete with a timeout.
303374

304-
*fut* can be a coroutine, a Task, or a Future-like object. If
305-
*fut* is a coroutine it is automatically scheduled as a
306-
:class:`Task`.
375+
If *fut* is a coroutine it is automatically scheduled as a Task.
307376

308377
*timeout* can either be ``None`` or a float or int number of seconds
309378
to wait for. If *timeout* is ``None``, block until the future
@@ -312,13 +381,17 @@ Timeouts
312381
If a timeout occurs, it cancels the task and raises
313382
:exc:`asyncio.TimeoutError`.
314383

315-
To avoid the task cancellation, wrap it in :func:`shield`.
384+
To avoid the task :meth:`cancellation <Task.cancel>`,
385+
wrap it in :func:`shield`.
316386

317387
The function will wait until the future is actually cancelled,
318388
so the total wait time may exceed the *timeout*.
319389

320390
If the wait is cancelled, the future *fut* is also cancelled.
321391

392+
The *loop* argument is deprecated and scheduled for removal
393+
in Python 4.0.
394+
322395
.. _asyncio_example_waitfor:
323396

324397
Example::
@@ -353,13 +426,18 @@ Waiting Primitives
353426
.. coroutinefunction:: wait(fs, \*, loop=None, timeout=None,\
354427
return_when=ALL_COMPLETED)
355428

356-
Wait for a set of coroutines, Tasks, or Futures to complete.
429+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
430+
sequence concurrently and block until the condition specified
431+
by *return_when*.
357432

358-
*fs* is a list of coroutines, Futures, and/or Tasks. Coroutines
359-
are automatically scheduled as :class:`Tasks <Task>`.
433+
If any awaitable in *fs* is a coroutine, it is automatically
434+
scheduled as a Task.
360435

361436
Returns two sets of Tasks/Futures: ``(done, pending)``.
362437

438+
The *loop* argument is deprecated and scheduled for removal
439+
in Python 4.0.
440+
363441
*timeout* (a float or int), if specified, can be used to control
364442
the maximum number of seconds to wait before returning.
365443

@@ -398,16 +476,18 @@ Waiting Primitives
398476

399477
.. function:: as_completed(fs, \*, loop=None, timeout=None)
400478

401-
Return an iterator of awaitables which return
402-
:class:`Future` instances.
479+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
480+
set concurrently. Return an iterator of :class:`Future` objects.
481+
Each Future object returned represents the earliest result
482+
from the set of the remaining awaitables.
403483

404484
Raises :exc:`asyncio.TimeoutError` if the timeout occurs before
405485
all Futures are done.
406486

407487
Example::
408488

409489
for f in as_completed(fs):
410-
result = await f
490+
earliest_result = await f
411491
# ...
412492

413493

@@ -418,7 +498,8 @@ Scheduling From Other Threads
418498

419499
Submit a coroutine to the given event loop. Thread-safe.
420500

421-
Return a :class:`concurrent.futures.Future` to access the result.
501+
Return a :class:`concurrent.futures.Future` to wait for the result
502+
from another OS thread.
422503

423504
This function is meant to be called from a different OS thread
424505
than the one where the event loop is running. Example::

Doc/library/asyncio.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
await asyncio.sleep(1)
1818
print('... World!')
1919

20+
# Python 3.7+
2021
asyncio.run(main())
2122

2223
asyncio is a library to write **concurrent** code using

Doc/tools/extensions/pyspecific.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ def handle_signature(self, sig, signode):
163163
return ret
164164

165165

166+
class PyAwaitableMixin(object):
167+
def handle_signature(self, sig, signode):
168+
ret = super(PyAwaitableMixin, self).handle_signature(sig, signode)
169+
signode.insert(0, addnodes.desc_annotation('awaitable ', 'awaitable '))
170+
return ret
171+
172+
166173
class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel):
167174
def run(self):
168175
self.name = 'py:function'
@@ -175,6 +182,18 @@ def run(self):
175182
return PyClassmember.run(self)
176183

177184

185+
class PyAwaitableFunction(PyAwaitableMixin, PyClassmember):
186+
def run(self):
187+
self.name = 'py:function'
188+
return PyClassmember.run(self)
189+
190+
191+
class PyAwaitableMethod(PyAwaitableMixin, PyClassmember):
192+
def run(self):
193+
self.name = 'py:method'
194+
return PyClassmember.run(self)
195+
196+
178197
class PyAbstractMethod(PyClassmember):
179198

180199
def handle_signature(self, sig, signode):
@@ -394,6 +413,8 @@ def setup(app):
394413
app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod)
395414
app.add_directive_to_domain('py', 'coroutinefunction', PyCoroutineFunction)
396415
app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod)
416+
app.add_directive_to_domain('py', 'awaitablefunction', PyAwaitableFunction)
417+
app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod)
397418
app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod)
398419
app.add_directive('miscnews', MiscNews)
399420
return {'version': '1.0', 'parallel_read_safe': True}

0 commit comments

Comments
 (0)