Vector 2 Vector 2
Vector 3 Vector 3
Vector 2 Vector 3
Group 6 Subtract
Rectangle 7 Rectangle 8 Rectangle 9
Rectangle 10
Vector
<- Back to Blog

Blog posts

2023-09-28

Unlocking New Workflows with Background Tasks

Reflex just released v0.2.8 earlier last week and with it, one of the most exciting features I've worked on: background tasks.

If you're a current developer of Reflex apps, you've no doubt noticed that slow events block the UI from receiving input. This is because Reflex intentionally processes events one at a time. While this is great for keeping your app's state consistent, it can be frustrating when you need to execute a long-running task and still keep the UI interactive and responsive to user input.

In previous releases, the main way to retain interactivity while running a long task was to explicitly chain steps of the calculation together using yield or return statements.

The following code example defines State.get_post which recursively queues itself to fetch 10 posts from an API, while still allowing over queued events to be processed in between.

class State(rx.State):
    last_id: int = 0
    posts: List[str] = []

    def get_post(self):
        if self.last_id < 10:
            response = httpx.get(
                f"https://dummyjson.com/products/{self.last_id}"
            )
            self.posts.append(response.text)
            self.last_id += 1
            return State.step

This works okay for some types of problems, but it complicates the code, hindering readability and future maintenance. Further, the event queue is still blocked while each step in the process runs. So while this does allow for some interactivity, it's not a complete solution.

A background task is a special type of async EventHandler that may run concurrently with other EventHandler functions. They mostly work like normal EventHandler functions on a State, however they must be async and they cannot directly modify the state.

Wait a sec... if they cannot modify the state, how are they useful?

Well, a background task can modify the state, but only inside of an async with self context block. Python 3.7 added support for async contextmanagers, and Reflex takes advantage of this feature to provide a safe way to modify the state from a background task.

So what does async with self actually do?

When a background task enters an async with self context block, it refreshes the state instance and takes an exclusive lock on it.

  • Inside the context block, the background task is guaranteed that self has the latest values and no other EventHandler functions are modifying the state.
  • After exiting the context block, an update is sent to the frontend to reflect the new state, and the lock is released, allowing any queued events to be processed.
  • Outside of the context block, the state may be stale, but other event handlers can execute while the task is awaiting long-running processes.

Rewriting the example above shows how background tasks work in practice:

class State(rx.State):
    posts: List[str] = []

    @rx.background
    async def get_posts(self):
        with httpx.AsyncClient() as client:
            for pid in range(10):
                response = await client.get(
                    f"https://dummyjson.com/products/{pid}"
                )
                async with self:
                    self.posts.append(response.text)

The background task can fetch all of the posts while allowing the app to continue processing UI events in the foreground. The only time the UI is blocked is during the short period of time where the response is being appended to the state.

For a more complete example with comparisons of the two styles, see the random-number-range app in the reflex-examples repository.

Aside from UI interactivity, the other motivation for using background tasks is to parallelize work within the app. For example the user can be monitoring responses from several API calls at once. Or the app can define different background tasks for a multi-view dashboard UI and have separate portions of the screen updating and processing simultaneously.

Full code for this example is available in reflex-examples/lorem-stream .

Background tasks is a powerful new feature that enables you to build long-running workflows that can respond to user input in real time. We hope you find unique and creative ways to use them in your apps.

Please see the docs for more information on background tasks and check out the complete v0.2.8 release notes for all the new features and bug fixes.

🔧 Happy Building 🚀

-- Reflex Team

The Reflex logo.

Site

HomeGalleryBlogChangelog

Join Newsletter

Get the latest updates and news about Reflex.

Join Newsletter

Get the latest updates and news about Reflex.

Copyright © 2024 Pynecone, Inc.