@@ -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
3633import asyncio
34+ import dataclasses
3735import time
3836import 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
92105def 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:
1191421 . Execute CPU-bound operations that would otherwise block the event loop
1201432 . Call synchronous libraries that don't have async equivalents
1211443 . 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