Skip to content

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:

  1. Cancels every pending task
  2. Waits up to task_cancellation_timeout_seconds (configurable in global settings) for them to finish
  3. 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)