Skip to content

Async runtime

PythonNative runs a single framework-wide asyncio event loop on a dedicated daemon thread. Every awaitable surface in the framework — use_async_effect, use_query, use_mutation, fetch, AsyncStorage, the awaitable native modules (Camera / Location / Notifications), and Animated composites — schedules its work on this loop.

Asyncio runtime for PythonNative.

PythonNative runs a single, framework-wide asyncio event loop on a dedicated daemon thread. Every awaitable surface in the framework — the async hooks (use_async_effect, use_query, use_mutation), the fetch HTTP client, AsyncStorage, the awaitable native modules (Camera / Location / Notifications), and awaited animations — schedules its work on this loop via run_async.

The reconciler is not asyncio-aware; it still runs synchronously on the platform main thread. Coroutines that want to mutate component state simply call the regular use_state setter, and the existing deferred-render path inside the screen host marshals the re-render onto the main thread. The runtime is therefore additive: it gives coroutines somewhere to live without changing the rendering contract.

Example
import asyncio

import pythonnative as pn


async def hello() -> str:
    await asyncio.sleep(0.1)
    return "hi"


future = pn.runtime.run_async(hello())
print(future.result(timeout=1.0))  # "hi"

Functions:

Name Description
get_loop

Return the framework-wide event loop, starting it on first use.

run_async

Schedule awaitable on the framework loop and return a thread future.

call_threadsafe

Schedule callback(*args) on the loop thread.

resolve_future

Set future's result from any thread (no-op if already done).

reject_future

Set future's exception from any thread (no-op if already done).

create_future

Create a future bound to the framework runtime loop.

call_on_main_thread

Run fn() on the platform UI thread.

get_loop

get_loop() -> AbstractEventLoop

Return the framework-wide event loop, starting it on first use.

The loop runs on a daemon thread ("pn-asyncio") and lives for the duration of the process. It is safe to call this from any thread.

Returns:

Type Description
AbstractEventLoop

The shared :class:asyncio.AbstractEventLoop.

run_async

run_async(awaitable: Awaitlike[T]) -> '_ThreadFuture[T]'

Schedule awaitable on the framework loop and return a thread future.

Use this when calling async code from synchronous code (e.g. an event handler, a hook setup function, or a test). The returned :class:concurrent.futures.Future is created by :func:asyncio.run_coroutine_threadsafe so it can be result()-ed from the calling thread and cancel()-ed from anywhere.

Parameters:

Name Type Description Default
awaitable Awaitlike[T]

Either a coroutine object (the typical case) or any awaitable. Awaitables that are not coroutines are wrapped with :func:asyncio.ensure_future on the loop.

required

Returns:

Type Description
'_ThreadFuture[T]'

A thread-safe future that resolves with the coroutine's return

'_ThreadFuture[T]'

value, or raises its exception.

Example
import pythonnative as pn

async def work():
    return 42

fut = pn.runtime.run_async(work())
assert fut.result(timeout=1.0) == 42

call_threadsafe

call_threadsafe(callback: Callable[..., Any], *args: Any) -> None

Schedule callback(*args) on the loop thread.

Thin wrapper around :meth:asyncio.AbstractEventLoop.call_soon_threadsafe. Useful from native delegates (which may fire on arbitrary threads) when you need to hop onto the runtime thread before touching asyncio primitives.

resolve_future

resolve_future(future: 'asyncio.Future[T]', value: T) -> None

Set future's result from any thread (no-op if already done).

Convenience used by every native delegate that wraps a callback into an awaitable: the delegate doesn't have to know which thread it's on, only that it must not race with cancellation.

Parameters:

Name Type Description Default
future 'asyncio.Future[T]'

An :class:asyncio.Future bound to the runtime loop.

required
value T

The value to deliver as the future's result.

required

reject_future

reject_future(future: 'asyncio.Future[Any]', error: BaseException) -> None

Set future's exception from any thread (no-op if already done).

create_future

create_future() -> 'asyncio.Future[Any]'

Create a future bound to the framework runtime loop.

Safe to call from any thread. The returned future is not attached to whatever loop is current on the caller; instead it lives on the framework's shared loop so any thread can call resolve_future / reject_future on it.

call_on_main_thread

call_on_main_thread(fn: Callable[[], None]) -> None

Run fn() on the platform UI thread.

  • iOS: dispatches fn onto the main dispatch queue via libdispatch.dispatch_async_f (called through :class:ctypes.PyDLL to keep the GIL held — see the _ios_call_on_main comment block for why this matters).
  • Android: posts a Runnable to Handler(Looper.getMainLooper()).
  • Desktop / tests: runs fn() inline.

Exceptions raised by fn are caught and printed; they must not propagate into UIKit / the Android Looper. If you need to surface a result back to the asyncio loop, do it via resolve_future from inside fn.

Parameters:

Name Type Description Default
fn Callable[[], None]

A zero-arg callable. Runs on the main thread when the platform's UI runtime is available, otherwise inline.

required

Pattern: bridge a sync handler into async code

import pythonnative as pn


@pn.component
def Toolbar():
    async def export():
        report = await build_report()
        await save_to_disk(report)

    return pn.Button("Export", on_click=lambda: pn.run_async(export()))

Next steps