Tonio
A multi-threaded async runtime for Python
Install / Use
/learn @gi0baro/TonioREADME
TonIO
TonIO is a multi-threaded async runtime for free-threaded Python, built in Rust on top of the mio crate, and inspired by tinyio, trio and tokio.
Warning: TonIO is currently a work in progress and in alpha state. The APIs are subtle to breaking changes.
Note: TonIO is available on free-threaded Python and Unix systems only.
TonIO supports both using yield and the more canonical async/await notations, with the latter being available as part of the tonio.colored module. Following code snippets show both the usages.
Warning: despite the fact TonIO supports
asyncandawaitnotations, it's not compatible with anyasyncioobject like futures and tasks.
In a nutshell
<table><tr><td>yield syntax
import tonio
def wait_and_add(x: int) -> int:
yield tonio.sleep(1)
return x + 1
def foo():
four, five = yield tonio.spawn(
wait_and_add(3),
wait_and_add(4)
)
return four, five
out = tonio.run(foo())
assert out == (4, 5)
</td><td>
await syntax
import tonio.colored as tonio
async def wait_and_add(x: int) -> int:
await tonio.sleep(1)
return x + 1
async def foo():
four, five = await tonio.spawn(
wait_and_add(3),
wait_and_add(4)
)
return four, five
out = tonio.run(foo())
assert out == (4, 5)
</td></tr></table>
Usage
Entrypoint
Every TonIO program consist of an entrypoint, which should be passed to the run method:
yield syntax
import tonio
def main():
yield
print("Hello world")
tonio.run(main())
</td><td>
await syntax
import tonio.colored as tonio
async def main():
await tonio.yield_now()
print("Hellow world")
tonio.run(main())
</td></tr></table>
TonIO also provides a main decorator, thus we can rewrite the previous example as:
yield syntax
import tonio
@tonio.main
def main():
yield
print("Hello world")
main()
</td><td>
await syntax
import tonio.colored as tonio
@tonio.main
async def main():
await tonio.yield_now()
print("Hello world")
main()
</td></tr></table>
Note: as you can see the
coloredmodule provides the additionalyield_nowcoroutine, a quick way to define a suspension point, given you cannot justyieldas in the non-colored notation.
Note: both
runandmaincan only be called once per program. To run the runtime multiple times in the same program, follow the section below.
Manually managing the runtime
TonIO also provides the runtime function, to manually manage the runtime lifecycle:
import tonio
def _run1():
...
async def _run2():
...
def main():
runtime = tonio.runtime()
runtime.run_until_complete(_run1())
runtime.run_until_complete(_run2())
Runtime options
The run, main and runtime methods accept options, specifically:
| option name | description | default |
| --- | --- | --- |
| context | enable contextvars usage in coroutines | False |
| signals | list of signals to listen to | |
| threads | Number of runtime threads | # of CPU cores |
| blocking_threadpool_size | Maximum number of blocking threads | 128 |
| blocking_threadpool_idle_ttl | Idle timeout for blocking threads (in seconds) | 30 |
Events
The core object in TonIO is Event. It's basically a wrapper around an atomic boolean flag, initialised with False. Event provides the following methods:
is_set(): return the value of the flagset(): set the flag toTrueclear(): set the flag toFalsewait(timeout=None): returns a coroutine you can yield on that unblocks when the flag is set toTrueor the timeout expires. Timeout is in seconds.
yield syntax
import tonio
@tonio.main
def main():
event = tonio.Event()
def setter():
yield tonio.sleep(1)
event.set()
tonio.spawn(setter())
yield event.wait()
</td><td>
await syntax
import tonio.colored as tonio
@tonio.main
async def main():
event = tonio.Event()
async def setter():
await tonio.sleep(1)
event.set()
tonio.spawn(setter())
await event.wait()
</td></tr></table>
Spawning tasks
TonIO provides the spawn method to schedule new coroutines onto the runtime:
yield syntax
import tonio
def doubv(v):
yield
return v * 2
@tonio.main
def main():
parallel = tonio.spawn(doubv(2), doubv(3))
v3 = yield doubv(4)
v1, v2 = yield parallel
print([v1, v2, v3])
</td><td>
await syntax
import tonio.colored as tonio
async def doubv(v):
await tonio.yield_now()
return v * 2
@tonio.main
async def main():
parallel = tonio.spawn(doubv(2), doubv(3))
v3 = await doubv(4)
v1, v2 = await parallel
print([v1, v2, v3])
</td></tr></table>
Coroutines passed to spawn get schedule onto the runtime immediately. Using yield or await on the return value of spawn just waits for the coroutines to complete and retreive the results.
Blocking tasks
TonIO provides the spawn_blocking method to schedule blocking operations onto the runtime:
yield syntax
import tonio
def read_file(path):
with open(file, "r") as f:
return f.read()
@tonio.main
def main():
file_data = yield tonio.spawn_blocking(
read_file,
"sometext.txt"
)
</td><td>
await syntax
import tonio.colored as tonio
def read_file(path):
with open(file, "r") as f:
return f.read()
@tonio.main
async def main():
file_data = await tonio.spawn_blocking(
read_file,
"sometext.txt"
)
</td></tr></table>
Running tasks from synchronous contexts
TonIO provides the block_on method to spawn coroutines from a synchronous context. It works the same way of spawn, except it accepts a single coroutine and it blocks the current thread until the coroutine is completed.
Warning: using
block_onfrom within a coroutine might produce a runtime deadlock.
Map utilities
TonIO provides the map and map_blocking utilities to spawn the same operation with an iterable of parameters:
yield syntax
import tonio
accum = []
def task(no):
yield tonio.sleep(0.5)
accum.append(no * 2)
@tonio.main
def main():
yield tonio.map(task, range(4))
</td><td>
await syntax
import tonio.colored as tonio
accum = []
async def task(no):
await tonio.sleep(0.5)
accum.append(no * 2)
@tonio.main
async def main():
await tonio.map(task, range(4))
</td></tr></table>
Scopes and cancellations
TonIO provides a scope context, that lets you cancel work spawned within it:
yield syntax
import tonio
def slow_push(target, sleep):
yield tonio.sleep(sleep)
target.append(True)
@tonio.main
def main():
values = []
with tonio.scope() as scope:
scope.spawn(_slow_push(values, 0.1))
scope.spawn(_slow_push(values, 2))
yield tonio.sleep(0.2)
scope.cancel()
yield scope()
assert len(values) == 1
</td><td>
await syntax
import tonio.colored as tonio
async def slow_push(target, sleep):
await tonio.sleep(sleep)
target.append(True)
@tonio.main
async def main():
values = []
async with tonio.scope() as scope:
scope.spawn(_slow_push(values, 0.1))
scope.spawn(_slow_push(values, 2))
await tonio.sleep(0.2)
scope.cancel()
assert len(values) == 1
</td></tr></table>
When you yield on the scope, it will wait for all the spawned coroutines to end. If the scope was canceled, then all the pending coroutines will be canceled.
Note: as you can see, the colored version of
scopedoesn't require to beawaited, as it will yield when exiting the context.
Time-related functions
tonio.time.time(): a function returning the runtime's clock (in seconds, microsecond resolution)tonio.time.sleep(delay): a coroutine you can yield on to sleep (delay is in seconds)tonio.time.timeout(coro, timeout): a coroutine you can yield on returning a tuple(output, success). If the coroutine succeeds in the given time then the pair(output, True)is returned. Otherwise this will return(None, False).
Note:
time.sleepis also exported to the maintoniomodule.
Note: all of the above functions are also present in
tonio.colored.timemodule.
Scheduling work
TonIO provides the time.interval function to create interval objects you can yield on a scheduled basis:
yield syntax
import tonio
from tonio import time
def some_task():
...
def scheduler():
interval = time.interval(1)
while True:
yield interval.tick()
tonio.spawn(some_task())
@tonio.main
def main():
tonio.spawn(scheduler())
# do some other work
</td><td>
await syntax
import tonio.colored as tonio
from tonio.colored import time
async def some_task():
...
async def scheduler():
interval = time.interval(1)
while True:
await interval.tick()
tonio.spawn(some_task())
@tonio.main
async def main():
tonio.spawn(scheduler())
# do some other work
</td></tr></table>
The interval method first argument is the interval in seconds resolution, and the method also accepts an optional at argument, to delay the first execution at a specific time (from the runtime's clock perspective):
from tonio import time
# tick every 500ms, with the first tick happening in 5 seconds from now
interval = time.interval(0.5, time.time() + 5)
Synchr
Related Skills
node-connect
352.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
352.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
