diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index f0c2dab..ab41a91 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -13,8 +13,8 @@ ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. The example given here are for terminal IPython, running async code in a -notebook interface or any other frontend using the Jupyter protocol will need to -use a newer version of IPykernel. The details of how async code runs in +notebook interface or any other frontend using the Jupyter protocol need to +IPykernel version 5.0 or above. The details of how async code runs in IPykernel will differ between IPython, IPykernel and their versions. When a supported library is used, IPython will automatically allow Futures and @@ -220,3 +220,97 @@ feel free to contribute improvements to this codebase and give us feedback. We invite you to thoroughly test this feature and report any unexpected behavior as well as propose any improvement. + +Using Autoawait in a notebook (IPykernel) +========================================= + +Update ipykernel to version 5.0 or greater:: + + pip install ipykernel ipython --upgrade + # or + conda install ipykernel ipython --upgrade + +This should automatically enable ``autoawait`` integration. Unlike terminal +IPython all code run on ``asynio`` eventloop, so creating a loop by hand will +not work, including with magics like ``%run`` or other framework that create +the eventloop themselves. In case like this you can try to use projects like +`nest_asyncio `_ and see discussion like `this one +`_ + +Difference between terminal IPython and IPykernel +================================================= + +The exact asynchronous code running behavior can varies between Terminal +IPython and IPykernel. The root cause of this behavior is due to IPykernel +having a _persistent_ ``asyncio`` loop running, while Terminal IPython start +and stop a loop for each code block. This can lead to surprising behavior in +some case if you are used to manipulate asyncio loop yourself, see for example +:ghissue:`11303` for a longer discussion but here are some of the astonishing +cases. + +This behavior is an implementation detail, and should not be relied upon. It +can change without warnings in future versions of IPython. + +In terminal IPython a loop is started for each code blocks only if there is top +level async code:: + + $ ipython + In [1]: import asyncio + ...: asyncio.get_event_loop() + Out[1]: <_UnixSelectorEventLoop running=False closed=False debug=False> + + In [2]: + + In [2]: import asyncio + ...: await asyncio.sleep(0) + ...: asyncio.get_event_loop() + Out[2]: <_UnixSelectorEventLoop running=True closed=False debug=False> + +See that ``running`` is ``True`` only in the case were we ``await sleep()`` + +In a Notebook, with ipykernel the asyncio eventloop is always running:: + + $ jupyter notebook + In [1]: import asyncio + ...: loop1 = asyncio.get_event_loop() + ...: loop1 + Out[1]: <_UnixSelectorEventLoop running=True closed=False debug=False> + + In [2]: loop2 = asyncio.get_event_loop() + ...: loop2 + Out[2]: <_UnixSelectorEventLoop running=True closed=False debug=False> + + In [3]: loop1 is loop2 + Out[3]: True + +In Terminal IPython background task are only processed while the foreground +task is running, and IIF the foreground task is async:: + + $ ipython + In [1]: import asyncio + ...: + ...: async def repeat(msg, n): + ...: for i in range(n): + ...: print(f"{msg} {i}") + ...: await asyncio.sleep(1) + ...: return f"{msg} done" + ...: + ...: asyncio.ensure_future(repeat("background", 10)) + Out[1]: :3>> + + In [2]: await asyncio.sleep(3) + background 0 + background 1 + background 2 + background 3 + + In [3]: import time + ...: time.sleep(5) + + In [4]: await asyncio.sleep(3) + background 4 + background 5 + background 6 + +In a Notebook, QtConsole, or any other frontend using IPykernel, background +task should behave as expected.