Task Bucket
self.task_bucket is each app's task manager — it tracks background work, offloads blocking calls to threads, and cleans everything up automatically when the app shuts down.
Spawning Background Tasks
Use spawn() to fire off a coroutine that runs independently of the current handler. The bucket tracks the task and cancels it on shutdown — you don't need to store the handle yourself:
# Fire off a background coroutine — the bucket tracks and cancels it on shutdown
self.task_bucket.spawn(self.poll_sensor(), name="poll_sensor")
spawn() returns the asyncio.Task if you need to check its status or cancel it manually.
Offloading Blocking Code
Use run_in_thread() to run a synchronous function in a thread pool without blocking the event loop. Await the result:
# Run a blocking call without freezing the event loop
data = await self.task_bucket.run_in_thread(self.expensive_sync_call)
self.logger.info("Got: %s", data)
Use this for anything that blocks: HTTP clients without async support, database drivers, file I/O, CPU-bound computation.
Normalizing Sync/Async Callables
make_async_adapter() wraps any callable — sync or async — into a consistent async callable. Sync functions are automatically routed through run_in_thread():
# Normalize a sync-or-async callable into an async callable
handler = self.task_bucket.make_async_adapter(self.maybe_sync_handler)
await handler() # always safe to await regardless of original type
This is useful when your app accepts user-provided callbacks that could be either sync or async.
Cross-Thread Communication
Posting to the Event Loop
post_to_loop() schedules a callable on the main event loop from any thread. Use this when code running in run_in_thread() needs to trigger an async action:
# Schedule a callback on the event loop from any thread
self.task_bucket.post_to_loop(self.on_data_ready, "sensor.temperature")
Running Async from Sync Code
run_sync() does the inverse — it runs an async coroutine from synchronous code by submitting it to the event loop and blocking until it completes:
# Inside a thread (run_in_thread or AppSync), call async code with run_sync
state = self.task_bucket.run_sync(self.api.get_state("sensor.temperature"))
Warning
run_sync() blocks the calling thread. Never call it from the event loop thread — it will deadlock. It's designed for use inside run_in_thread() callbacks or AppSync lifecycle methods where you need to make an async API call.
Shutdown Behavior
All tasks tracked by the bucket are cancelled when the app shuts down. Hassette:
- Cancels every pending task
- Waits up to
task_cancellation_timeout_seconds(configurable in global settings) for them to finish - Logs any tasks that don't respond to cancellation
You don't need to clean up spawned tasks manually — the bucket handles it.
See Also
- Apps Overview — core capabilities and common patterns
- Lifecycle — when shutdown happens and in what order
- App Cache — for persisting data across restarts (task bucket is for in-memory work)