Concurrent message handling (notebook)¶
async-kernel provides concurrent message handling with a separate message handler queue for each channel and msg_type. Execute requests also provide a separate queue per subshell.
Callers¶
async-kernel provides two Callers for the shell and control channels. The shell's Caller is
associated with the thread where the kernel is started, normally this is the MainThread.
Interface¶
The interface is a singleton which provides configuration for all classes that subclass from HasInterface.
The interface coordinates the startup and shutdown of the kernel and connections to communicate with the
kernel.
In CPython the interface can be started from the command line with a connection file. The interface is configurable via settings defined in the kernel spec, or by using the traitlets style configuration files. Multiple connections are allow on the connection.
Connections¶
Connections are provided to communicate with the kernel in the same thread. There is no limit to the number of connections, however increasing the number of connections will slow down the interface because all iopub messages are sent on every connection. Additional connection subclasses can created by subclassing from Connection.
ZMQ Socket Based connection¶
Currently zmq sockets are the only external connection provided. A ZMQConnection is automatically started when the interface is started in CPython.
Shell messaging¶
Both execute_request and com_msg are always handled in the shell's thread (normally the MainThread).
All other messages on the shell channel are handled in the control thread.
Control messaging¶
All messages on the control channel are handled in the control thread.
import threading
import ipywidgets as ipw
from aiologic import Event
Execute request run mode¶
The run mode of execute_request can be modified to run an execute_request separately as a task or thread.
There are a few options to modify the run mode.
- Metadata
- Directly in code
- tags
- Message header (in custom messages)
Warning
Only Jupyter lab is known to allow concurrent execution of cells.
Code for example¶
- This example requires ipywidgets
- Ensure you are running an async-kernel
Lets define a function that we'll reuse for the remainder of the notebook.
async def demo():
print(f"Thread name: '{threading.current_thread().name}'")
button = ipw.Button(description="Finish")
event = Event()
button.on_click(lambda _: event.set())
display(button)
await event
button.close()
print(f"Finished ... thread name: '{threading.current_thread().name}'")
return "Finished"
Lets run it normally (queue)
await demo()
Thread name: 'MainThread'
Button(description='Finish', style=ButtonStyle())
Run mode: task¶
task mode instructs the kernel to execute the code in a task separate to the queue, Both task and thread execute modes can be started when the kernel is busy executing. There is no imposed limitation on the number of tasks (or threads) that can be run concurrently.
See also the Caller example on how to call directly.
# task
# Tip: try running this cell while the previous cell is still busy.
await demo()
Thread name: 'MainThread'
Button(description='Finish', style=ButtonStyle())
Run mode: thread¶
# This time we'll use the tag to run the cell in a worker thread
await demo()
Thread name: 'MainThread'
Button(description='Finish', style=ButtonStyle())
# thread
%callers # magic provided by async-kernel
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 ← current
We can also specify CallerCreateOptions as part of the top line
# thread name="My thread"
%callers
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 My thread ✓ My thread 140582299096768 ← current
Asynchronous magic¶
Asynchronous line (%) and cell (%%) magic functions are supported. Any line or cell magic that returns an awaitable is awaited before proceeding.
thread magic¶
This will run the code in a thread. When no settings are provided a cell worker thread is used.
Comparing thread magic with thread run mode¶
- thread magic (
%%thread) is an asynchronous magic that executes the associated code in a separate thread. - thread run mode (
# thread) instructs the kernel to run the entire cell in a separate thread, bypassing the shell execute request queue.
# Run the magic 'callers' in a caller worker thread.
%thread %callers
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 ← current My thread ✓ My thread 140582299096768
To specify a thread (caller) by name
%%thread name="My executor"
%callers
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 My thread ✓ My thread 140582299096768 My executor ✓ My executor 140581997115072 ← current
Many of arguments accepted on Caller.get are also supported. Let's use a thread with a trio backend.
%%thread name="My trio executor" backend=trio
%callers
import trio
await trio.sleep(0)
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 My thread ✓ My thread 140582299096768 My executor ✓ My executor 140581997115072 My trio executor ✓ My trio executor 140581980333760 ← current
Specify the backend¶
Code that is written for a specific backend ('asyncio' or 'trio') can be run in the same thread with one of the following:
Line magic - The code following the magic on the same line is run using the specified backend.
%trio%asyncio
Cell magic - The code block is run using the specified backend.
%%trio%asyncio
Note: trio must be installed for this demo to work.
import asyncio # noqa: F401 # pyright: ignore[reportUnusedImport]
%asyncio await asyncio.sleep(0) # This code gets run in an asyncio task
%trio await trio.sleep(0) # trio run as line magic
%%trio # trio cell magic
def print_info():
from aiologic.lowlevel import current_async_library
print(f"""
Kernel backend: {get_ipython().kernel.parent.backend}
Current backend: { current_async_library()}
""")
print_info()
await trio.sleep(0)
%callers
%asyncio print_info()
%asyncio %callers
await trio.sleep(0)
Kernel backend: asyncio
Current backend: trio
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 ← current Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 My thread ✓ My thread 140582299096768 My executor ✓ My executor 140581997115072 My trio executor ✓ My trio executor 140581980333760
Kernel backend: asyncio
Current backend: asyncio
Name Running Protected Thread Caller ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── MainThread ✓ 🔐 MainThread 140582611734656 ← current Control ✓ 🔐 Control 140582557943488 async-kernel worker of 140582611734656 ✓ Thread-2 (async_kernel_caller) 140582315878080 My thread ✓ My thread 140582299096768 My executor ✓ My executor 140581997115072 My trio executor ✓ My trio executor 140581980333760