Skip to content

Commit 73c0006

Browse files
miss-islington1st1
andauthored
bpo-33649: Add a high-level section about Futures; few quick fixes (GH-9403)
Co-authored-by: Elvis Pranskevichus <[email protected]> (cherry picked from commit 4715039) Co-authored-by: Yury Selivanov <[email protected]>
1 parent 1a89cb5 commit 73c0006

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
@@ -983,7 +983,7 @@ Availability: Unix.
983983
Executing code in thread or process pools
984984
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
985985

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

988988
Arrange for *func* to be called in the specified executor.
989989

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,13 +192,28 @@ Creating Tasks
140192

141193
.. function:: create_task(coro)
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
The task is executed in the loop returned by :func:`get_running_loop`,
147199
:exc:`RuntimeError` is raised if there is no running loop in
148200
current thread.
149201

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

152219

@@ -160,6 +227,9 @@ Sleeping
160227
If *result* is provided, it is returned to the caller
161228
when the coroutine completes.
162229

230+
The *loop* argument is deprecated and scheduled for removal
231+
in Python 4.0.
232+
163233
.. _asyncio_example_sleep:
164234

165235
Example of coroutine displaying the current date every second
@@ -183,36 +253,31 @@ Sleeping
183253
Running Tasks Concurrently
184254
==========================
185255

186-
.. function:: gather(\*fs, loop=None, return_exceptions=False)
256+
.. awaitablefunction:: gather(\*fs, loop=None, return_exceptions=False)
187257

188-
Return a Future aggregating results from the given coroutine objects,
189-
Tasks, or Futures.
258+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
259+
sequence *concurrently*.
190260

191-
If all Tasks/Futures are completed successfully, the result is an
192-
aggregate list of returned values. The result values are in the
193-
order of the original *fs* sequence.
261+
If any awaitable in *fs* is a coroutine, it is automatically
262+
scheduled as a Task.
194263

195-
All coroutines in the *fs* list are automatically
196-
scheduled as :class:`Tasks <Task>`.
264+
If all awaitables are completed successfully, the result is an
265+
aggregate list of returned values. The order of result values
266+
corresponds to the order of awaitables in *fs*.
197267

198-
If *return_exceptions* is ``True``, exceptions in the Tasks/Futures
199-
are treated the same as successful results, and gathered in the
200-
result list. Otherwise, the first raised exception is immediately
201-
propagated to the returned Future.
268+
If *return_exceptions* is ``True``, exceptions are treated the
269+
same as successful results, and aggregated in the result list.
270+
Otherwise, the first raised exception is immediately propagated
271+
to the task that awaits on ``gather()``.
202272

203-
If the outer Future is *cancelled*, all submitted Tasks/Futures
273+
If ``gather`` is *cancelled*, all submitted awaitables
204274
(that have not completed yet) are also *cancelled*.
205275

206-
If any child is *cancelled*, it is treated as if it raised
207-
:exc:`CancelledError` -- the outer Future is **not** cancelled in
208-
this case. This is to prevent the cancellation of one submitted
209-
Task/Future to cause other Tasks/Futures to be cancelled.
210-
211-
All futures must share the same event loop.
212-
213-
.. versionchanged:: 3.7
214-
If the *gather* itself is cancelled, the cancellation is
215-
propagated regardless of *return_exceptions*.
276+
If any Task or Future from the *fs* sequence is *cancelled*, it is
277+
treated as if it raised :exc:`CancelledError` -- the ``gather()``
278+
call is **not** cancelled in this case. This is to prevent the
279+
cancellation of one submitted Task/Future to cause other
280+
Tasks/Futures to be cancelled.
216281

217282
.. _asyncio_example_gather:
218283

@@ -229,6 +294,7 @@ Running Tasks Concurrently
229294
print(f"Task {name}: factorial({number}) = {f}")
230295

231296
async def main():
297+
# Schedule three calls *concurrently*:
232298
await asyncio.gather(
233299
factorial("A", 2),
234300
factorial("B", 3),
@@ -249,17 +315,21 @@ Running Tasks Concurrently
249315
# Task C: Compute factorial(4)...
250316
# Task C: factorial(4) = 24
251317

318+
.. versionchanged:: 3.7
319+
If the *gather* itself is cancelled, the cancellation is
320+
propagated regardless of *return_exceptions*.
321+
252322

253323
Shielding Tasks From Cancellation
254324
=================================
255325

256-
.. coroutinefunction:: shield(fut, \*, loop=None)
326+
.. awaitablefunction:: shield(fut, \*, loop=None)
257327

258-
Wait for a Future/Task while protecting it from being cancelled.
328+
Protect an :ref:`awaitable object <asyncio-awaitables>`
329+
from being :meth:`cancelled <Task.cancel>`.
259330

260331
*fut* can be a coroutine, a Task, or a Future-like object. If
261-
*fut* is a coroutine it is automatically scheduled as a
262-
:class:`Task`.
332+
*fut* is a coroutine it is automatically scheduled as a Task.
263333

264334
The statement::
265335

@@ -293,11 +363,10 @@ Timeouts
293363

294364
.. coroutinefunction:: wait_for(fut, timeout, \*, loop=None)
295365

296-
Wait for a coroutine, Task, or Future to complete with timeout.
366+
Wait for the *fut* :ref:`awaitable <asyncio-awaitables>`
367+
to complete with a timeout.
297368

298-
*fut* can be a coroutine, a Task, or a Future-like object. If
299-
*fut* is a coroutine it is automatically scheduled as a
300-
:class:`Task`.
369+
If *fut* is a coroutine it is automatically scheduled as a Task.
301370

302371
*timeout* can either be ``None`` or a float or int number of seconds
303372
to wait for. If *timeout* is ``None``, block until the future
@@ -306,13 +375,17 @@ Timeouts
306375
If a timeout occurs, it cancels the task and raises
307376
:exc:`asyncio.TimeoutError`.
308377

309-
To avoid the task cancellation, wrap it in :func:`shield`.
378+
To avoid the task :meth:`cancellation <Task.cancel>`,
379+
wrap it in :func:`shield`.
310380

311381
The function will wait until the future is actually cancelled,
312382
so the total wait time may exceed the *timeout*.
313383

314384
If the wait is cancelled, the future *fut* is also cancelled.
315385

386+
The *loop* argument is deprecated and scheduled for removal
387+
in Python 4.0.
388+
316389
.. _asyncio_example_waitfor:
317390

318391
Example::
@@ -347,13 +420,18 @@ Waiting Primitives
347420
.. coroutinefunction:: wait(fs, \*, loop=None, timeout=None,\
348421
return_when=ALL_COMPLETED)
349422

350-
Wait for a set of coroutines, Tasks, or Futures to complete.
423+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
424+
sequence concurrently and block until the condition specified
425+
by *return_when*.
351426

352-
*fs* is a list of coroutines, Futures, and/or Tasks. Coroutines
353-
are automatically scheduled as :class:`Tasks <Task>`.
427+
If any awaitable in *fs* is a coroutine, it is automatically
428+
scheduled as a Task.
354429

355430
Returns two sets of Tasks/Futures: ``(done, pending)``.
356431

432+
The *loop* argument is deprecated and scheduled for removal
433+
in Python 4.0.
434+
357435
*timeout* (a float or int), if specified, can be used to control
358436
the maximum number of seconds to wait before returning.
359437

@@ -392,16 +470,18 @@ Waiting Primitives
392470

393471
.. function:: as_completed(fs, \*, loop=None, timeout=None)
394472

395-
Return an iterator of awaitables which return
396-
:class:`Future` instances.
473+
Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
474+
set concurrently. Return an iterator of :class:`Future` objects.
475+
Each Future object returned represents the earliest result
476+
from the set of the remaining awaitables.
397477

398478
Raises :exc:`asyncio.TimeoutError` if the timeout occurs before
399479
all Futures are done.
400480

401481
Example::
402482

403483
for f in as_completed(fs):
404-
result = await f
484+
earliest_result = await f
405485
# ...
406486

407487

@@ -412,7 +492,8 @@ Scheduling From Other Threads
412492

413493
Submit a coroutine to the given event loop. Thread-safe.
414494

415-
Return a :class:`concurrent.futures.Future` to access the result.
495+
Return a :class:`concurrent.futures.Future` to wait for the result
496+
from another OS thread.
416497

417498
This function is meant to be called from a different OS thread
418499
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)