Unverified Kaydet (Commit) 47150395 authored tarafından Yury Selivanov's avatar Yury Selivanov Kaydeden (comit) GitHub

bpo-33649: Add a high-level section about Futures; few quick fixes (GH-9403)

Co-authored-by: 's avatarElvis Pranskevichus <elvis@magic.io>
üst a3c88ef1
...@@ -989,7 +989,7 @@ Availability: Unix. ...@@ -989,7 +989,7 @@ Availability: Unix.
Executing code in thread or process pools Executing code in thread or process pools
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. coroutinemethod:: loop.run_in_executor(executor, func, \*args) .. awaitablemethod:: loop.run_in_executor(executor, func, \*args)
Arrange for *func* to be called in the specified executor. Arrange for *func* to be called in the specified executor.
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
Futures Futures
======= =======
*Future* objects are used to bridge low-level callback-based code *Future* objects are used to bridge **low-level callback-based code**
with high-level async/await code. with high-level async/await code.
......
...@@ -103,6 +103,31 @@ To actually run a coroutine asyncio provides three main mechanisms: ...@@ -103,6 +103,31 @@ To actually run a coroutine asyncio provides three main mechanisms:
world world
finished at 17:14:34 finished at 17:14:34
.. _asyncio-awaitables:
Awaitables
==========
We say that an object is an *awaitable* object if it can be used
in an :keyword:`await` expression.
.. rubric:: Coroutines and Tasks
Python coroutines are *awaitables*::
async def nested():
return 42
async def main():
# Will print "42":
print(await nested())
*Tasks* are used to schedule coroutines *concurrently*.
See the previous :ref:`section <coroutine>` for an introduction
to coroutines and tasks.
Note that in this documentation the term "coroutine" can be used for Note that in this documentation the term "coroutine" can be used for
two closely related concepts: two closely related concepts:
...@@ -112,14 +137,41 @@ two closely related concepts: ...@@ -112,14 +137,41 @@ two closely related concepts:
*coroutine function*. *coroutine function*.
.. rubric:: Futures
There is a dedicated section about the :ref:`asyncio Future object
<asyncio-futures>`, but the concept is fundamental to asyncio so
it needs a brief introduction in this section.
A Future is a special **low-level** awaitable object that represents
an **eventual result** of an asynchronous operation.
Future objects in asyncio are needed to allow callback-based code
to be used with async/await.
Normally, **there is no need** to create Future objects at the
application level code.
Future objects, sometimes exposed by libraries and some asyncio
APIs, should be awaited::
async def main():
await function_that_returns_a_future_object()
# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
Running an asyncio Program Running an asyncio Program
========================== ==========================
.. function:: run(coro, \*, debug=False) .. function:: run(coro, \*, debug=False)
This function runs the passed coroutine, taking care of This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous managing the asyncio event loop and *finalizing asynchronous
generators. generators*.
This function cannot be called when another asyncio event loop is This function cannot be called when another asyncio event loop is
running in the same thread. running in the same thread.
...@@ -140,8 +192,8 @@ Creating Tasks ...@@ -140,8 +192,8 @@ Creating Tasks
.. function:: create_task(coro, \*, name=None) .. function:: create_task(coro, \*, name=None)
Wrap the *coro* :ref:`coroutine <coroutine>` into a task and schedule Wrap the *coro* :ref:`coroutine <coroutine>` into a Task and
its execution. Return the task object. schedule its execution. Return the Task object.
If *name* is not ``None``, it is set as the name of the task using If *name* is not ``None``, it is set as the name of the task using
:meth:`Task.set_name`. :meth:`Task.set_name`.
...@@ -150,6 +202,21 @@ Creating Tasks ...@@ -150,6 +202,21 @@ Creating Tasks
:exc:`RuntimeError` is raised if there is no running loop in :exc:`RuntimeError` is raised if there is no running loop in
current thread. current thread.
This function has been **added in Python 3.7**. Prior to
Python 3.7, the low-level :func:`asyncio.ensure_future` function
can be used instead::
async def coro():
...
# In Python 3.7+
task = asyncio.create_task(coro())
...
# This works in all Python versions but is less readable
task = asyncio.ensure_future(coro())
...
.. versionadded:: 3.7 .. versionadded:: 3.7
.. versionchanged:: 3.8 .. versionchanged:: 3.8
...@@ -166,6 +233,9 @@ Sleeping ...@@ -166,6 +233,9 @@ Sleeping
If *result* is provided, it is returned to the caller If *result* is provided, it is returned to the caller
when the coroutine completes. when the coroutine completes.
The *loop* argument is deprecated and scheduled for removal
in Python 4.0.
.. _asyncio_example_sleep: .. _asyncio_example_sleep:
Example of coroutine displaying the current date every second Example of coroutine displaying the current date every second
...@@ -189,36 +259,31 @@ Sleeping ...@@ -189,36 +259,31 @@ Sleeping
Running Tasks Concurrently Running Tasks Concurrently
========================== ==========================
.. function:: gather(\*fs, loop=None, return_exceptions=False) .. awaitablefunction:: gather(\*fs, loop=None, return_exceptions=False)
Return a Future aggregating results from the given coroutine objects, Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
Tasks, or Futures. sequence *concurrently*.
If all Tasks/Futures are completed successfully, the result is an If any awaitable in *fs* is a coroutine, it is automatically
aggregate list of returned values. The result values are in the scheduled as a Task.
order of the original *fs* sequence.
All coroutines in the *fs* list are automatically If all awaitables are completed successfully, the result is an
scheduled as :class:`Tasks <Task>`. aggregate list of returned values. The order of result values
corresponds to the order of awaitables in *fs*.
If *return_exceptions* is ``True``, exceptions in the Tasks/Futures If *return_exceptions* is ``True``, exceptions are treated the
are treated the same as successful results, and gathered in the same as successful results, and aggregated in the result list.
result list. Otherwise, the first raised exception is immediately Otherwise, the first raised exception is immediately propagated
propagated to the returned Future. to the task that awaits on ``gather()``.
If the outer Future is *cancelled*, all submitted Tasks/Futures If ``gather`` is *cancelled*, all submitted awaitables
(that have not completed yet) are also *cancelled*. (that have not completed yet) are also *cancelled*.
If any child is *cancelled*, it is treated as if it raised If any Task or Future from the *fs* sequence is *cancelled*, it is
:exc:`CancelledError` -- the outer Future is **not** cancelled in treated as if it raised :exc:`CancelledError` -- the ``gather()``
this case. This is to prevent the cancellation of one submitted call is **not** cancelled in this case. This is to prevent the
Task/Future to cause other Tasks/Futures to be cancelled. cancellation of one submitted Task/Future to cause other
Tasks/Futures to be cancelled.
All futures must share the same event loop.
.. versionchanged:: 3.7
If the *gather* itself is cancelled, the cancellation is
propagated regardless of *return_exceptions*.
.. _asyncio_example_gather: .. _asyncio_example_gather:
...@@ -235,6 +300,7 @@ Running Tasks Concurrently ...@@ -235,6 +300,7 @@ Running Tasks Concurrently
print(f"Task {name}: factorial({number}) = {f}") print(f"Task {name}: factorial({number}) = {f}")
async def main(): async def main():
# Schedule three calls *concurrently*:
await asyncio.gather( await asyncio.gather(
factorial("A", 2), factorial("A", 2),
factorial("B", 3), factorial("B", 3),
...@@ -255,17 +321,21 @@ Running Tasks Concurrently ...@@ -255,17 +321,21 @@ Running Tasks Concurrently
# Task C: Compute factorial(4)... # Task C: Compute factorial(4)...
# Task C: factorial(4) = 24 # Task C: factorial(4) = 24
.. versionchanged:: 3.7
If the *gather* itself is cancelled, the cancellation is
propagated regardless of *return_exceptions*.
Shielding Tasks From Cancellation Shielding Tasks From Cancellation
================================= =================================
.. coroutinefunction:: shield(fut, \*, loop=None) .. awaitablefunction:: shield(fut, \*, loop=None)
Wait for a Future/Task while protecting it from being cancelled. Protect an :ref:`awaitable object <asyncio-awaitables>`
from being :meth:`cancelled <Task.cancel>`.
*fut* can be a coroutine, a Task, or a Future-like object. If *fut* can be a coroutine, a Task, or a Future-like object. If
*fut* is a coroutine it is automatically scheduled as a *fut* is a coroutine it is automatically scheduled as a Task.
:class:`Task`.
The statement:: The statement::
...@@ -299,11 +369,10 @@ Timeouts ...@@ -299,11 +369,10 @@ Timeouts
.. coroutinefunction:: wait_for(fut, timeout, \*, loop=None) .. coroutinefunction:: wait_for(fut, timeout, \*, loop=None)
Wait for a coroutine, Task, or Future to complete with timeout. Wait for the *fut* :ref:`awaitable <asyncio-awaitables>`
to complete with a timeout.
*fut* can be a coroutine, a Task, or a Future-like object. If If *fut* is a coroutine it is automatically scheduled as a Task.
*fut* is a coroutine it is automatically scheduled as a
:class:`Task`.
*timeout* can either be ``None`` or a float or int number of seconds *timeout* can either be ``None`` or a float or int number of seconds
to wait for. If *timeout* is ``None``, block until the future to wait for. If *timeout* is ``None``, block until the future
...@@ -312,13 +381,17 @@ Timeouts ...@@ -312,13 +381,17 @@ Timeouts
If a timeout occurs, it cancels the task and raises If a timeout occurs, it cancels the task and raises
:exc:`asyncio.TimeoutError`. :exc:`asyncio.TimeoutError`.
To avoid the task cancellation, wrap it in :func:`shield`. To avoid the task :meth:`cancellation <Task.cancel>`,
wrap it in :func:`shield`.
The function will wait until the future is actually cancelled, The function will wait until the future is actually cancelled,
so the total wait time may exceed the *timeout*. so the total wait time may exceed the *timeout*.
If the wait is cancelled, the future *fut* is also cancelled. If the wait is cancelled, the future *fut* is also cancelled.
The *loop* argument is deprecated and scheduled for removal
in Python 4.0.
.. _asyncio_example_waitfor: .. _asyncio_example_waitfor:
Example:: Example::
...@@ -353,13 +426,18 @@ Waiting Primitives ...@@ -353,13 +426,18 @@ Waiting Primitives
.. coroutinefunction:: wait(fs, \*, loop=None, timeout=None,\ .. coroutinefunction:: wait(fs, \*, loop=None, timeout=None,\
return_when=ALL_COMPLETED) return_when=ALL_COMPLETED)
Wait for a set of coroutines, Tasks, or Futures to complete. Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
sequence concurrently and block until the condition specified
by *return_when*.
*fs* is a list of coroutines, Futures, and/or Tasks. Coroutines If any awaitable in *fs* is a coroutine, it is automatically
are automatically scheduled as :class:`Tasks <Task>`. scheduled as a Task.
Returns two sets of Tasks/Futures: ``(done, pending)``. Returns two sets of Tasks/Futures: ``(done, pending)``.
The *loop* argument is deprecated and scheduled for removal
in Python 4.0.
*timeout* (a float or int), if specified, can be used to control *timeout* (a float or int), if specified, can be used to control
the maximum number of seconds to wait before returning. the maximum number of seconds to wait before returning.
...@@ -398,8 +476,10 @@ Waiting Primitives ...@@ -398,8 +476,10 @@ Waiting Primitives
.. function:: as_completed(fs, \*, loop=None, timeout=None) .. function:: as_completed(fs, \*, loop=None, timeout=None)
Return an iterator of awaitables which return Run :ref:`awaitable objects <asyncio-awaitables>` in the *fs*
:class:`Future` instances. set concurrently. Return an iterator of :class:`Future` objects.
Each Future object returned represents the earliest result
from the set of the remaining awaitables.
Raises :exc:`asyncio.TimeoutError` if the timeout occurs before Raises :exc:`asyncio.TimeoutError` if the timeout occurs before
all Futures are done. all Futures are done.
...@@ -407,7 +487,7 @@ Waiting Primitives ...@@ -407,7 +487,7 @@ Waiting Primitives
Example:: Example::
for f in as_completed(fs): for f in as_completed(fs):
result = await f earliest_result = await f
# ... # ...
...@@ -418,7 +498,8 @@ Scheduling From Other Threads ...@@ -418,7 +498,8 @@ Scheduling From Other Threads
Submit a coroutine to the given event loop. Thread-safe. Submit a coroutine to the given event loop. Thread-safe.
Return a :class:`concurrent.futures.Future` to access the result. Return a :class:`concurrent.futures.Future` to wait for the result
from another OS thread.
This function is meant to be called from a different OS thread This function is meant to be called from a different OS thread
than the one where the event loop is running. Example:: than the one where the event loop is running. Example::
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
await asyncio.sleep(1) await asyncio.sleep(1)
print('... World!') print('... World!')
# Python 3.7+
asyncio.run(main()) asyncio.run(main())
asyncio is a library to write **concurrent** code using asyncio is a library to write **concurrent** code using
......
...@@ -163,6 +163,13 @@ class PyCoroutineMixin(object): ...@@ -163,6 +163,13 @@ class PyCoroutineMixin(object):
return ret return ret
class PyAwaitableMixin(object):
def handle_signature(self, sig, signode):
ret = super(PyAwaitableMixin, self).handle_signature(sig, signode)
signode.insert(0, addnodes.desc_annotation('awaitable ', 'awaitable '))
return ret
class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel): class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel):
def run(self): def run(self):
self.name = 'py:function' self.name = 'py:function'
...@@ -175,6 +182,18 @@ class PyCoroutineMethod(PyCoroutineMixin, PyClassmember): ...@@ -175,6 +182,18 @@ class PyCoroutineMethod(PyCoroutineMixin, PyClassmember):
return PyClassmember.run(self) return PyClassmember.run(self)
class PyAwaitableFunction(PyAwaitableMixin, PyClassmember):
def run(self):
self.name = 'py:function'
return PyClassmember.run(self)
class PyAwaitableMethod(PyAwaitableMixin, PyClassmember):
def run(self):
self.name = 'py:method'
return PyClassmember.run(self)
class PyAbstractMethod(PyClassmember): class PyAbstractMethod(PyClassmember):
def handle_signature(self, sig, signode): def handle_signature(self, sig, signode):
...@@ -394,6 +413,8 @@ def setup(app): ...@@ -394,6 +413,8 @@ def setup(app):
app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod) app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod)
app.add_directive_to_domain('py', 'coroutinefunction', PyCoroutineFunction) app.add_directive_to_domain('py', 'coroutinefunction', PyCoroutineFunction)
app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod) app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod)
app.add_directive_to_domain('py', 'awaitablefunction', PyAwaitableFunction)
app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod)
app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod) app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod)
app.add_directive('miscnews', MiscNews) app.add_directive('miscnews', MiscNews)
return {'version': '1.0', 'parallel_read_safe': True} return {'version': '1.0', 'parallel_read_safe': True}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment