Skip to main content

Background Tasks

Wave apps are servers based on asyncio, Python's library for Asynchronous I/O, and lets you develop and deploy high-performance applications.

Your @app query handler is invoked every time a user performs some action in your app's UI - access the page, reload it, click a button, access a menu, enter text, and so on. Performing blocking operations in your handler will hang your app's server and make your app's UI appear unresponsive until the blocking operation completes.

In some cases, blocking calls can be pushed to the background to improve concurrency. To achieve this, the Wave API provides two lightweight wrappers over asyncio.run_in_executor(): q.run() and q.exec().

Here is an example of a function that blocks:

import time

def blocking_function(seconds) -> str:
time.sleep(seconds) # Blocks!
return f'Done!'

To call the above function from an app, don't do this:

@app('/demo')
async def serve(q: Q):
# ...
message = blocking_function(42)
# ...

Instead, do this:

@app('/demo')
async def serve(q: Q):
# ...
message = await q.run(blocking_function, 42)
# ...

q.run() runs the blocking function in the background, in-process.

Depending on your use case, you might want to use a separate process pool or thread pool from Python's multiprocessing library, like this:

import concurrent.futures

@app('/demo')
async def serve(q: Q):
# ...
with concurrent.futures.ThreadPoolExecutor() as pool:
message = await q.exec(pool, blocking_function, 42)
# ...

q.exec() accepts a custom process pool or thread pool to run the blocking function.

import concurrent.futures

@app('/demo')
async def serve(q: Q):
# ...
with concurrent.futures.ProcessPoolExecutor() as pool:
message = await q.exec(pool, blocking_function, seconds)
# ...
tip

Apps that make calls to external services or APIs are better off replacing blocking HTTP clients like requests with non-blocking clients like HTTPX.

To update UI from within a background job, see this example or a detailed blog post.