Caveats¶
Return type depends on whether a loop is running¶
An asyncified call returns a started Future when an event loop is already running,
and a coroutine when no loop is running (it starts when awaited). Both are
awaitable, so await my_func(...) works in either case. Be aware of the
difference if you inspect or store the return value without awaiting it.
Cancellation cannot stop work that already started¶
unblock runs work via asyncio.loop.run_in_executor(). Cancelling the
returned awaitable follows the same rules: work that has not yet started in the
pool can be cancelled, but work already running in a thread or process cannot be
forcibly stopped. Design long-running callables to check for their own stop
condition if you need cooperative cancellation.
Process pool: what can and cannot be sent¶
With executor="process", the callable and its arguments are pickled and sent
to a worker process, and the result is pickled back. This means:
The callable must be importable (defined at module level).
unblockmakes the decorator form work by sending a small reference (module and qualified name) that the worker re-resolves – so@asyncify(executor="process")works on ordinary module-level functions.Closures, lambdas, and locally-defined functions cannot be pickled by the standard library and are rejected immediately with
unblock.UnblockErrorrather than failing later inside the pool. Define the function at module level, or useexecutor="thread".Arguments and return values must be picklable.
Work runs on a pickled copy of any objects you pass; mutations made in the worker do not propagate back to the parent process.
This is a usability constraint, not a security boundary: the worker processes are
your own child processes. The only general caution is that passing data you
deserialized from an untrusted source carries whatever risk that data already had
– a property of your own data handling, not of unblock.
Iteration cost¶
Asynchronous iteration produces one item per executor round-trip, sequentially. For very large or hot iterations, the per-item overhead can be significant; this is the trade-off for not blocking the event loop on a synchronous iterator.
Resource lifecycle¶
The default thread and process pools are created on first use and shut down
automatically at interpreter exit. Call unblock.shutdown() to release them
early; after shutdown, the defaults are recreated only once you supply a new pool
via unblock.set_thread_pool() or unblock.set_process_pool(). If you
supply your own executors, you remain responsible for their lifecycle.
Event loop support¶
unblock requires an asyncio-compatible event loop (the default asyncio loop or
uvloop). It does not support trio, curio etc which have their own event loops.