Concurrent message handling (notebook)¶
async-kernel is capable of concurrent message handling. It provides a separate message handler for each channel and msg_type.
As messages are received they are queued for execution by the message handler.
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.
In CPython, the Caller for the control channel is started as a child of the shell caller with
it's own thread and asynchronous backend ('asyncio' or 'trio').
ZMQ message handling (CPython)¶
The kernel interface also starts two additional threads dedicated to receive the messages on
the shell and control channels, the specific function call is session.recv(socket, mode=zmq.BLOCKY, copy=False).
Each thread blocks until a new message is received on the zmq socket. When a message is received,
the kernel's message_handler is called with detail of the message (Job), the channel and a function
that is to be used to send the response corresponding to the message. The message_handler obtains
the dedicated handler according to the message_type, run_mode, channel and subshell_id and
schedules execution of the dedicated message handler in Caller's thread.
Shell messaging¶
Both execute_request and com_msg are always run in the shell's thread (normally the MainThread).
All other messages on the shell channel are run 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
from async_kernel import utils
kernel = utils.get_kernel()
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 Thread
await demo()
Thread name: 'MainThread'
Button(description='Finish', style=ButtonStyle())
# thread
%callers # magic provided by async-kernel
Name Running Protected Thread Caller
─────────────────────────────────────────────────────────────────────────────────────────────
Shell ✓ 🔐 MainThread 140641610026640
Control ✓ 🔐 Control 140641565533120
async_kernel_caller ✓ async_kernel_caller 140641524893456 ← current