Skip to content

Commit 82451a7

Browse files
authored
fix devon's run_in_thread docs (#1338)
1 parent e7df5ba commit 82451a7

File tree

1 file changed

+62
-48
lines changed

1 file changed

+62
-48
lines changed

docs/api-reference/utils.md

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ Reflex provides utility functions to help with common tasks in your applications
99

1010
## run_in_thread
1111

12-
The `run_in_thread` function allows you to run a non-async function in a separate thread, which is useful for preventing long-running operations from blocking the UI event queue.
12+
The `run_in_thread` function allows you to run a **non-async** function in a separate thread, which is useful for preventing long-running operations from blocking the UI event queue.
1313

1414
```python
15-
async def run_in_thread(func: Callable, *, timeout: float | None = None) -> Any
15+
async def run_in_thread(func: Callable) -> Any
1616
```
1717

1818
### Parameters
1919

2020
- `func`: The non-async function to run in a separate thread.
21-
- `timeout`: Maximum number of seconds to wait for the function to complete. If `None` (default), wait indefinitely.
2221

2322
### Returns
2423

@@ -27,76 +26,88 @@ async def run_in_thread(func: Callable, *, timeout: float | None = None) -> Any
2726
### Raises
2827

2928
- `ValueError`: If the function is an async function.
30-
- `asyncio.TimeoutError`: If the function execution exceeds the specified timeout.
3129

3230
### Usage
3331

34-
3532
```python demo exec id=run_in_thread_demo
3633
import asyncio
34+
import dataclasses
3735
import time
3836
import reflex as rx
3937

4038

41-
class RunInThreadState(rx.State):
39+
def quick_blocking_function():
40+
time.sleep(0.5)
41+
return "Quick task completed successfully!"
42+
43+
44+
def slow_blocking_function():
45+
time.sleep(3.0)
46+
return "This should never be returned due to timeout!"
47+
48+
49+
@dataclasses.dataclass
50+
class TaskInfo:
4251
result: str = "No result yet"
4352
status: str = "Idle"
53+
54+
55+
class RunInThreadState(rx.State):
56+
tasks: list[TaskInfo] = []
4457

4558
@rx.event(background=True)
4659
async def run_quick_task(self):
4760
"""Run a quick task that completes within the timeout."""
4861
async with self:
49-
self.status = "Running quick task..."
50-
51-
def quick_function():
52-
time.sleep(0.5)
53-
return "Quick task completed successfully!"
62+
task_ix = len(self.tasks)
63+
self.tasks.append(TaskInfo(status="Running quick task..."))
64+
task_info = self.tasks[task_ix]
5465

5566
try:
56-
# Run with a timeout of 2 seconds (more than enough time)
57-
result = await rx.run_in_thread(quick_function, timeout=2.0)
67+
result = await rx.run_in_thread(quick_blocking_function)
5868
async with self:
59-
self.result = result
60-
self.status = "Idle"
69+
task_info.result = result
70+
task_info.status = "Complete"
6171
except Exception as e:
6272
async with self:
63-
self.result = f"Error: {str(e)}"
64-
self.status = "Idle"
73+
task_info.result = f"Error: {str(e)}"
74+
task_info.status = "Failed"
6575

6676
@rx.event(background=True)
6777
async def run_slow_task(self):
6878
"""Run a slow task that exceeds the timeout."""
6979
async with self:
70-
self.status = "Running slow task..."
71-
72-
def slow_function():
73-
time.sleep(3.0)
74-
return "This should never be returned due to timeout!"
80+
task_ix = len(self.tasks)
81+
self.tasks.append(TaskInfo(status="Running slow task..."))
82+
task_info = self.tasks[task_ix]
7583

7684
try:
7785
# Run with a timeout of 1 second (not enough time)
78-
result = await rx.run_in_thread(slow_function, timeout=1.0)
86+
result = await asyncio.wait_for(
87+
rx.run_in_thread(slow_blocking_function),
88+
timeout=1.0,
89+
)
7990
async with self:
80-
self.result = result
81-
self.status = "Idle"
91+
task_info.result = result
92+
task_info.status = "Complete"
8293
except asyncio.TimeoutError:
8394
async with self:
84-
self.result = "Task timed out after 1 second!"
85-
self.status = "Idle"
95+
# Warning: even though we stopped waiting for the task,
96+
# it may still be running in thread
97+
task_info.result = "Task timed out after 1 second!"
98+
task_info.status = "Timeout"
8699
except Exception as e:
87100
async with self:
88-
self.result = f"Error: {str(e)}"
89-
self.status = "Idle"
101+
task_info.result = f"Error: {str(e)}"
102+
task_info.status = "Failed"
90103

91104

92105
def run_in_thread_example():
93106
return rx.vstack(
94107
rx.heading("run_in_thread Example", size="3"),
95-
rx.text("Status: ", RunInThreadState.status),
96-
rx.text("Result: ", RunInThreadState.result),
97108
rx.hstack(
98109
rx.button(
99-
"Run Quick Task (completes within timeout)",
110+
"Run Quick Task",
100111
on_click=RunInThreadState.run_quick_task,
101112
color_scheme="green",
102113
),
@@ -106,6 +117,18 @@ def run_in_thread_example():
106117
color_scheme="red",
107118
),
108119
),
120+
rx.vstack(
121+
rx.foreach(
122+
RunInThreadState.tasks.reverse()[:10],
123+
lambda task: rx.hstack(
124+
rx.text(task.status),
125+
rx.spacer(),
126+
rx.text(task.result),
127+
),
128+
),
129+
align="start",
130+
width="100%",
131+
),
109132
width="100%",
110133
align_items="start",
111134
spacing="4",
@@ -119,7 +142,6 @@ Use `run_in_thread` when you need to:
119142
1. Execute CPU-bound operations that would otherwise block the event loop
120143
2. Call synchronous libraries that don't have async equivalents
121144
3. Prevent long-running operations from blocking UI responsiveness
122-
4. Set a maximum execution time for potentially slow operations
123145

124146
### Example: Processing a Large File
125147

@@ -132,25 +154,17 @@ class FileProcessingState(rx.State):
132154

133155
@rx.event(background=True)
134156
async def process_large_file(self):
135-
self.progress = "Processing file..."
157+
async with self:
158+
self.progress = "Processing file..."
136159

137160
def process_file():
138161
# Simulate processing a large file
139162
time.sleep(5)
140163
return "File processed successfully!"
141164

142-
try:
143-
# Set a timeout of 10 seconds
144-
result = await rx.run_in_thread(process_file, timeout=10.0)
145-
async with self:
146-
self.progress = result
147-
except asyncio.TimeoutError:
148-
async with self:
149-
self.progress = "File processing timed out!"
165+
# Save the result to a local variable to avoid blocking the event loop.
166+
result = await rx.run_in_thread(process_file)
167+
async with self:
168+
# Then assign the local result to the state while holding the lock.
169+
self.progress = result
150170
```
151-
152-
### Notes
153-
154-
- The function passed to `run_in_thread` must be a regular (non-async) function.
155-
- Consider setting appropriate timeouts to prevent indefinite blocking.
156-
- Handle `asyncio.TimeoutError` exceptions to gracefully manage timeouts.

0 commit comments

Comments
 (0)