From 8fb10ee858332ef4ee483fde67b1478d391f3c5a Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Mon, 24 Nov 2025 02:15:13 +0500 Subject: [PATCH 01/12] Add async generators best practices section --- Doc/library/asyncio-dev.rst | 102 ++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 7831b613bd4a60..f4e7721a27b34e 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -248,3 +248,105 @@ Output in debug mode:: File "../t.py", line 4, in bug raise Exception("not consumed") Exception: not consumed + +Asynchronous generators best practices +====================================== + +By :term:`asynchronous generator` in this section we will mean +an :term:`asynchronous generator iterator` that returned by +:term:`asynchronous generator` function. + +Manually close the generator +---------------------------- + +If an asynchronous generator happens to exit early by :keyword:`break`, the caller +task being cancelled, or other exceptions, the generator's async cleanup code +will run and possibly raise exceptions or access context variables in an +unexpected context--perhaps after the lifetime of tasks it depends, or +during the event loop shutdown when the async-generator garbage collection hook +is called. + +To prevent this, it is recommended to explicitly close the async generator by +calling :meth:`~agen.aclose` method, or using :func:`contextlib.aclosing` context +manager:: + + import asyncio + import contextlib + + async def gen(): + yield 1 + yield 2 + + async def func(): + async with contextlib.aclosing(gen()) as g: + async for x in g: + break + + asyncio.run(func()) + + +Only create a generator when a loop is already running +------------------------------------------------------ + +As said abovew, if an asynchronous generator is not resumed before it is +finalized, then any finalization procedures will be delayed. The event loop +handles this situation and doing it best to call async generator-iterator's +:meth:`~agen.aclose` at the proper moment, thus allowing any pending +:keyword:`!finally` clauses to execute. + +Then it is recomended to create async generators only after the event loop +has already been created. + + +Avoid iterating and closing the same generator concurrently +----------------------------------------------------------- + +The async generators allow to be reentered while another +:meth:`~agen.aclose`/:meth:`~agen.aclose`/:meth:`~agen.aclose` call is in +progress. This may lead to an inconsistent state of the async generator +and cause errors. + +Let's consider following example:: + + import asyncio + + async def consumer(): + for idx in range(100): + await asyncio.sleep(0) + message = yield idx + print('received', message) + + async def amain(): + agenerator = consumer() + await agenerator.asend(None) + + fa = asyncio.create_task(agenerator.asend('A')) + fb = asyncio.create_task(agenerator.asend('B')) + await fa + await fb + + asyncio.run(amain()) + +Output:: + + received A + Traceback (most recent call last): + File "test.py", line 38, in + asyncio.run(amain()) + ~~~~~~~~~~~^^^^^^^^^ + File "Lib\asyncio\runners.py", line 204, in run + return runner.run(main) + ~~~~~~~~~~^^^^^^ + File "Lib\asyncio\runners.py", line 127, in run + return self._loop.run_until_complete(task) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ + File "Lib\asyncio\base_events.py", line 719, in run_until_complete + return future.result() + ~~~~~~~~~~~~~^^ + File "test.py", line 36, in amain + await fb + RuntimeError: anext(): asynchronous generator is already running + + +Therefore, it is recommended to avoid using the async generators in parallel +tasks or in the multiple event loops. From ae50f3f91d272de8998b07d2cc8dea0549dbee91 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Mon, 24 Nov 2025 10:32:22 +0500 Subject: [PATCH 02/12] Update Doc/library/asyncio-dev.rst Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Doc/library/asyncio-dev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index f4e7721a27b34e..cb3d980747f5d2 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -288,7 +288,7 @@ manager:: Only create a generator when a loop is already running ------------------------------------------------------ -As said abovew, if an asynchronous generator is not resumed before it is +As said above, if an asynchronous generator is not resumed before it is finalized, then any finalization procedures will be delayed. The event loop handles this situation and doing it best to call async generator-iterator's :meth:`~agen.aclose` at the proper moment, thus allowing any pending From 99f148c3f77e0c6ac9b3418cec3b8732b3548c7a Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Mon, 24 Nov 2025 10:36:09 +0500 Subject: [PATCH 03/12] Fix spacing between sections --- Doc/library/asyncio-dev.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index cb3d980747f5d2..129be11895e8ab 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -249,6 +249,7 @@ Output in debug mode:: raise Exception("not consumed") Exception: not consumed + Asynchronous generators best practices ====================================== @@ -284,7 +285,6 @@ manager:: asyncio.run(func()) - Only create a generator when a loop is already running ------------------------------------------------------ @@ -297,7 +297,6 @@ handles this situation and doing it best to call async generator-iterator's Then it is recomended to create async generators only after the event loop has already been created. - Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- From 11242b452d9da59a728e0711aaa13d7cb35e7045 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 27 Nov 2025 00:46:27 +0500 Subject: [PATCH 04/12] Apply suggestions from code review Co-authored-by: Guido van Rossum --- Doc/library/asyncio-dev.rst | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 129be11895e8ab..a029a7802d87f7 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -254,7 +254,7 @@ Asynchronous generators best practices ====================================== By :term:`asynchronous generator` in this section we will mean -an :term:`asynchronous generator iterator` that returned by +an :term:`asynchronous generator iterator` that is returned by an :term:`asynchronous generator` function. Manually close the generator @@ -262,13 +262,12 @@ Manually close the generator If an asynchronous generator happens to exit early by :keyword:`break`, the caller task being cancelled, or other exceptions, the generator's async cleanup code -will run and possibly raise exceptions or access context variables in an -unexpected context--perhaps after the lifetime of tasks it depends, or +will run in an unexpected context -- perhaps after the lifetime of tasks it depends on, or during the event loop shutdown when the async-generator garbage collection hook is called. To prevent this, it is recommended to explicitly close the async generator by -calling :meth:`~agen.aclose` method, or using :func:`contextlib.aclosing` context +calling the :meth:`~agen.aclose` method, or using a :func:`contextlib.aclosing` context manager:: import asyncio @@ -281,7 +280,7 @@ manager:: async def func(): async with contextlib.aclosing(gen()) as g: async for x in g: - break + break # Don't iterate until the end asyncio.run(func()) @@ -290,9 +289,9 @@ Only create a generator when a loop is already running As said above, if an asynchronous generator is not resumed before it is finalized, then any finalization procedures will be delayed. The event loop -handles this situation and doing it best to call async generator-iterator's -:meth:`~agen.aclose` at the proper moment, thus allowing any pending -:keyword:`!finally` clauses to execute. +handles this situation and doing its best to call the async generator-iterator's +:meth:`~agen.aclose` method at the proper moment, thus allowing any pending +:keyword:`!finally` clauses to run. Then it is recomended to create async generators only after the event loop has already been created. @@ -300,10 +299,10 @@ has already been created. Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- -The async generators allow to be reentered while another +Async generators may to be reentered while another :meth:`~agen.aclose`/:meth:`~agen.aclose`/:meth:`~agen.aclose` call is in progress. This may lead to an inconsistent state of the async generator -and cause errors. +and can cause errors. Let's consider following example:: @@ -347,5 +346,5 @@ Output:: RuntimeError: anext(): asynchronous generator is already running -Therefore, it is recommended to avoid using the async generators in parallel -tasks or in the multiple event loops. +Therefore, it is recommended to avoid using async generators in parallel +tasks or from multiple event loops. From 3556aefd9e7732aa15285f9e7216ccca75a01c9e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 27 Nov 2025 00:54:56 +0500 Subject: [PATCH 05/12] Fix copy-paste typo --- Doc/library/asyncio-dev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index a029a7802d87f7..f4bf34202064c4 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -300,7 +300,7 @@ Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- Async generators may to be reentered while another -:meth:`~agen.aclose`/:meth:`~agen.aclose`/:meth:`~agen.aclose` call is in +:meth:`~agen.anext`/:meth:`~agen.athrow`/:meth:`~agen.aclose` call is in progress. This may lead to an inconsistent state of the async generator and can cause errors. From a70038a976e364adf7dd8ae966c5d9ab7b536f83 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 27 Nov 2025 02:41:38 +0500 Subject: [PATCH 06/12] Update section about iterating agen after running event loop --- Doc/library/asyncio-dev.rst | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index f4bf34202064c4..20543a52bfcd0d 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -257,6 +257,7 @@ By :term:`asynchronous generator` in this section we will mean an :term:`asynchronous generator iterator` that is returned by an :term:`asynchronous generator` function. + Manually close the generator ---------------------------- @@ -284,18 +285,32 @@ manager:: asyncio.run(func()) + Only create a generator when a loop is already running ------------------------------------------------------ -As said above, if an asynchronous generator is not resumed before it is -finalized, then any finalization procedures will be delayed. The event loop -handles this situation and doing its best to call the async generator-iterator's -:meth:`~agen.aclose` method at the proper moment, thus allowing any pending -:keyword:`!finally` clauses to run. - -Then it is recomended to create async generators only after the event loop +It is recommended to create asynchronous generators only after the event loop has already been created. +To ensure that asynchronous generators close reliably, the event loop uses the +:func:`sys.set_asyncgen_hooks` function to register callback functions. These +callbacks update the list of running asynchronous generators to keep it in a +consistent state. + +When the :meth:`loop.shutdown_asyncgens() ` +function is called, the running generators are stopped gracefully, and the +list is cleared. + +The asynchronous generator calls the corresponding system hook when on the +first iteration. At the same time, the generator remembers that the hook was +called and don't call it twice. + +So, if the iteration begins before the event loop is created, the event loop +will not be able to add it to its list of active generators because the hooks +will be set after the generator tries to call it. Consequently, the event loop +will not be able to terminate the generator if necessary. + + Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- From 52565087e44060611a2c84d02bf33d9329d6f916 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Fri, 20 Feb 2026 18:43:03 +0500 Subject: [PATCH 07/12] Address reviews --- Doc/library/asyncio-dev.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 20543a52bfcd0d..7d5449861c4d04 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -253,23 +253,24 @@ Output in debug mode:: Asynchronous generators best practices ====================================== -By :term:`asynchronous generator` in this section we will mean -an :term:`asynchronous generator iterator` that is returned by an -:term:`asynchronous generator` function. +Writing correct and efficient asyncio code requires avoiding some known gotchas. +This section outlines the best practices you need to know. +Following these practices will save you hours of debugging. Manually close the generator ---------------------------- -If an asynchronous generator happens to exit early by :keyword:`break`, the caller -task being cancelled, or other exceptions, the generator's async cleanup code -will run in an unexpected context -- perhaps after the lifetime of tasks it depends on, or -during the event loop shutdown when the async-generator garbage collection hook -is called. +It is recommended to manually close the +:term:`asynchronous generator `. If the generator +exits early by exception raised in the for-loop body, for example, +the generator's async cleanup code will run in an unexpected context. This could +happen after the lifetime of tasks it depends on, or during the event loop +shutdown when the async-generator garbage collection hook is called. To prevent this, it is recommended to explicitly close the async generator by -calling the :meth:`~agen.aclose` method, or using a :func:`contextlib.aclosing` context -manager:: +calling the :meth:`~agen.aclose` method, or by using a :func:`contextlib.aclosing` +context manager:: import asyncio import contextlib @@ -289,8 +290,9 @@ manager:: Only create a generator when a loop is already running ------------------------------------------------------ -It is recommended to create asynchronous generators only after the event loop -has already been created. +It is recommended to create +:term:`asynchronous generators ` only after +the event loop has already been created. To ensure that asynchronous generators close reliably, the event loop uses the :func:`sys.set_asyncgen_hooks` function to register callback functions. These From 97ccca09025e7cf0059c12d500c28ef773c92ab2 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 26 Feb 2026 01:43:08 +0500 Subject: [PATCH 08/12] Add example for broken finalizers --- Doc/library/asyncio-dev.rst | 67 +++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 7d5449861c4d04..36ff5f3c2be1b9 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -286,6 +286,67 @@ context manager:: asyncio.run(func()) +As said above, the cleanup code for these asynchronous generators is defered. +We can construct an example program to show that the finalization of the +asynchronous generator is executed in an unexpected order:: + + import asyncio + work_done = False + + async def cursor(): + try: + yield 1 + finally: + assert work_done + + async def rows(): + global work_done + try: + yield 2 + finally: + await asyncio.sleep(0.1) # immitate some async work + work_done = True + + + async def main(): + async for c in cursor(): + async for r in rows(): + break + break + + asyncio.run(main()) + +For this example we get the following output:: + + unhandled exception during asyncio.run() shutdown + task: ()> exception=AssertionError()> + Traceback (most recent call last): + File "example.py", line 6, in cursor + yield 1 + asyncio.exceptions.CancelledError + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "example.py", line 8, in cursor + assert work_done + ^^^^^^^^^ + AssertionError + +The ``cursor()`` asynchronous generator was finalized before the ``rows`` +generator, which we did not expect. + +Example can be fixed by explicitly closing the +``cursor`` and ``rows`` async-generators:: + + async def main(): + async with contextlib.aclosing(cursor()) as cursor_gen: + async for c in cursor_gen: + async with contextlib.aclosing(rows()) as rows_gen: + async for r in rows_gen: + break + break + Only create a generator when a loop is already running ------------------------------------------------------ @@ -349,13 +410,13 @@ Output:: File "test.py", line 38, in asyncio.run(amain()) ~~~~~~~~~~~^^^^^^^^^ - File "Lib\asyncio\runners.py", line 204, in run + File "Lib/asyncio/runners.py", line 204, in run return runner.run(main) ~~~~~~~~~~^^^^^^ - File "Lib\asyncio\runners.py", line 127, in run + File "Lib/asyncio/runners.py", line 127, in run return self._loop.run_until_complete(task) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ - File "Lib\asyncio\base_events.py", line 719, in run_until_complete + File "Lib/asyncio/base_events.py", line 719, in run_until_complete return future.result() ~~~~~~~~~~~~~^^ File "test.py", line 36, in amain From 6ebf2945c7357ea504fcca2417bcb9c9c6d69e73 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 26 Feb 2026 01:45:22 +0500 Subject: [PATCH 09/12] Fix --- Doc/library/asyncio-dev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 36ff5f3c2be1b9..ee2a429cfd627b 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -378,7 +378,7 @@ Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- Async generators may to be reentered while another -:meth:`~agen.anext`/:meth:`~agen.athrow`/:meth:`~agen.aclose` call is in +:meth:`~agen.anext` / :meth:`~agen.athrow` / :meth:`~agen.aclose` call is in progress. This may lead to an inconsistent state of the async generator and can cause errors. From 100d9ac6211d62fe8f52590ca0414c338395b54f Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 26 Feb 2026 01:50:46 +0500 Subject: [PATCH 10/12] Fix anext --- Doc/library/asyncio-dev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index ee2a429cfd627b..fee7131e44b401 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -378,7 +378,7 @@ Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- Async generators may to be reentered while another -:meth:`~agen.anext` / :meth:`~agen.athrow` / :meth:`~agen.aclose` call is in +:meth:`~agen.__anext__` / :meth:`~agen.athrow` / :meth:`~agen.aclose` call is in progress. This may lead to an inconsistent state of the async generator and can cause errors. From d910ee76bcefbfcab3152efc679093d4a5806079 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Fri, 27 Feb 2026 00:53:29 +0500 Subject: [PATCH 11/12] Add example --- Doc/library/asyncio-dev.rst | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index fee7131e44b401..0ed7c45984646f 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -373,6 +373,49 @@ will not be able to add it to its list of active generators because the hooks will be set after the generator tries to call it. Consequently, the event loop will not be able to terminate the generator if necessary. +Consider following example:: + + import asyncio + + async def agenfn(): + try: + yield 10 + finally: + await asyncio.sleep(0) + + + with asyncio.Runner() as runner: + agen = agenfn() + print(runner.run(anext(agen))) + del agen + +Output:: + + 10 + Exception ignored while closing generator : + Traceback (most recent call last): + File "example.py", line 13, in + del agen + ^^^^ + RuntimeError: async generator ignored GeneratorExit + +This example can be fixed as follow:: + + import asyncio + + async def agenfn(): + try: + yield 10 + finally: + await asyncio.sleep(0) + + async def main(): + agen = agenfn() + print(await anext(agen)) + del agen + + asyncio.run(main()) + Avoid iterating and closing the same generator concurrently ----------------------------------------------------------- From b5c3ddc0674530efae6779fbf3ad9279c55bfdb4 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 21 Mar 2026 15:44:10 +0500 Subject: [PATCH 12/12] Fix wording --- Doc/library/asyncio-dev.rst | 81 +++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 0ed7c45984646f..f3409bcd2df648 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -253,23 +253,23 @@ Output in debug mode:: Asynchronous generators best practices ====================================== -Writing correct and efficient asyncio code requires avoiding some known gotchas. -This section outlines the best practices you need to know. -Following these practices will save you hours of debugging. +Writing correct and efficient asyncio code requires awareness of certain pitfalls. +This section outlines essential best practices that can save you hours of debugging. -Manually close the generator ----------------------------- +Close asynchronous generators explicitly +---------------------------------------- It is recommended to manually close the -:term:`asynchronous generator `. If the generator -exits early by exception raised in the for-loop body, for example, -the generator's async cleanup code will run in an unexpected context. This could -happen after the lifetime of tasks it depends on, or during the event loop -shutdown when the async-generator garbage collection hook is called. - -To prevent this, it is recommended to explicitly close the async generator by -calling the :meth:`~agen.aclose` method, or by using a :func:`contextlib.aclosing` +:term:`asynchronous generator `. If a generator +exits early - for example, due to an exception raised in the body of +an ``async for`` loop - its asynchronous cleanup code may run in an +unexpected context. This can occur after the tasks it depends on have completed, +or during the event loop shutdown when the async-generator's garbage collection +hook is called. + +To avoid this, explicitly close the generator by calling its +:meth:`~agen.aclose` method, or use the :func:`contextlib.aclosing` context manager:: import asyncio @@ -286,9 +286,9 @@ context manager:: asyncio.run(func()) -As said above, the cleanup code for these asynchronous generators is defered. -We can construct an example program to show that the finalization of the -asynchronous generator is executed in an unexpected order:: +As noted above, the cleanup code for these asynchronous generators is deferred. +The following example demonstrates that the finalization of an asynchronous +generator can occur in an unexpected order:: import asyncio work_done = False @@ -316,7 +316,7 @@ asynchronous generator is executed in an unexpected order:: asyncio.run(main()) -For this example we get the following output:: +For this example, we get the following output:: unhandled exception during asyncio.run() shutdown task: ()> exception=AssertionError()> @@ -334,9 +334,9 @@ For this example we get the following output:: AssertionError The ``cursor()`` asynchronous generator was finalized before the ``rows`` -generator, which we did not expect. +generator - an unexpected behavior. -Example can be fixed by explicitly closing the +The example can be fixed by explicitly closing the ``cursor`` and ``rows`` async-generators:: async def main(): @@ -348,12 +348,12 @@ Example can be fixed by explicitly closing the break -Only create a generator when a loop is already running ------------------------------------------------------- +Create asynchronous generators only when the event loop is running +------------------------------------------------------------------ It is recommended to create :term:`asynchronous generators ` only after -the event loop has already been created. +the event loop has been created. To ensure that asynchronous generators close reliably, the event loop uses the :func:`sys.set_asyncgen_hooks` function to register callback functions. These @@ -361,19 +361,20 @@ callbacks update the list of running asynchronous generators to keep it in a consistent state. When the :meth:`loop.shutdown_asyncgens() ` -function is called, the running generators are stopped gracefully, and the +function is called, the running generators are stopped gracefully and the list is cleared. -The asynchronous generator calls the corresponding system hook when on the -first iteration. At the same time, the generator remembers that the hook was -called and don't call it twice. +The asynchronous generator invokes the corresponding system hook during its +first iteration. At the same time, the generator records that the hook has +been called and does not call it again. -So, if the iteration begins before the event loop is created, the event loop -will not be able to add it to its list of active generators because the hooks -will be set after the generator tries to call it. Consequently, the event loop -will not be able to terminate the generator if necessary. +Therefore, if iteration begins before the event loop is created, +the event loop will not be able to add the generator to its list of active +generators because the hooks are set after the generator attempts to call them. +Consequently, the event loop will not be able to terminate the generator +if necessary. -Consider following example:: +Consider the following example:: import asyncio @@ -399,7 +400,7 @@ Output:: ^^^^ RuntimeError: async generator ignored GeneratorExit -This example can be fixed as follow:: +This example can be fixed as follows:: import asyncio @@ -417,15 +418,15 @@ This example can be fixed as follow:: asyncio.run(main()) -Avoid iterating and closing the same generator concurrently ------------------------------------------------------------ +Avoid concurrent iteration and closure of the same generator +------------------------------------------------------------ -Async generators may to be reentered while another +Async generators may be reentered while another :meth:`~agen.__anext__` / :meth:`~agen.athrow` / :meth:`~agen.aclose` call is in -progress. This may lead to an inconsistent state of the async generator -and can cause errors. +progress. This may lead to an inconsistent state of the async generator and can +cause errors. -Let's consider following example:: +Let's consider the following example:: import asyncio @@ -467,5 +468,5 @@ Output:: RuntimeError: anext(): asynchronous generator is already running -Therefore, it is recommended to avoid using async generators in parallel -tasks or from multiple event loops. +Therefore, it is recommended to avoid using asynchronous generators in parallel +tasks or across multiple event loops.