Concurrency: choosing a tool¶
Work that takes time — a search, a file read, a long import — must not run on the UI thread, or the window stops painting until it finishes. Its result must not reach a widget from the thread that produced it either.
Both halves already have answers, and none of them involve writing marshalling code by hand. Find your situation below.
Which tool¶
| Your situation | Reach for | Read |
|---|---|---|
A value derived from another, asynchronously — the map whose transform takes time. A SearchBar that searches as you type |
switch_map |
Async State |
| I/O in an event handler, and the screen just waits for it | an async handler, awaiting the call — no thread involved |
|
| Heavy work in an event handler, and the screen just waits for it | an async handler, await asyncio.to_thread(...) — a thread, but the runtime owns it |
Thread Safety |
| The same heavy work, but it should report progress and be cancellable | a hand-written worker thread | Background Work |
| Values arriving faster than the screen needs them | debounce() / throttle() |
Practical Controls |
| Getting a worker's result onto the UI thread | nothing — an observable write is marshalled for you | Thread Safety |
| A consumer that must see every intermediate value, not the newest | dispatch=False |
Thread Safety |
Testing an async handler |
await app.idle(), await app.wait_for(...) |
Testing async flows |
| Testing a write that came from a worker thread | app.settle() |
Thread Safety |