十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NiceGUI 进度条实战:用 `run.cpu_bound` 与 `ui.timer` 为重量级计算任务构建实时进度反馈

NiceGUI 进度条实战:用 `run.cpu_bound` 与 `ui.timer` 为重量级计算任务构建实时进度反馈 NiceGUI 进度条实战用run.cpu_bound与ui.timer为重量级计算任务构建实时进度反馈【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui本文以 NiceGUI 官方示例 examples/progress 为主线完整剖析耗时计算 实时进度条这一经典场景的落地方式后台进程执行 CPU 密集任务通过multiprocessing.Queue回传进度前端由ui.timer轮询驱动ui.linear_progress平滑刷新。读完本文你将掌握 NiceGUI 中进程池调用run.cpu_bound、跨进程进度通信、定时器轮询与进度条元素配置的组合用法并能直接复用到文件处理、模型推理、数据清洗等任何重型计算场景。一、示例概览文档说了什么原文档 README.md 内容极为精炼核心只有一句话Demonstrate a progress bar for heavy computations.即演示如何为重量级计算显示进度条。但麻雀虽小五脏俱全对应的 main.py 是一个约 40 行的自包含可运行程序完整覆盖了 NiceGUI 处理耗时任务的四条关键链路用run.cpu_bound把 CPU 密集函数调度到独立进程执行避免阻塞事件循环用multiprocessing.Manager().Queue()作为跨进程进度通道用ui.timer周期性轮询队列并把进度写回进度条用ui.linear_progress渲染进度配合props(instant-feedback)获得即时反馈。二、完整代码与逐段精讲下面是对 main.py 的完整复刻保留了原示例的全部逻辑仅补充注释#!/usr/bin/env python3 import time from multiprocessing import Manager, Queue from nicegui import run, ui def heavy_computation(q: Queue) - str: Run some heavy computation that updates the progress bar through the queue. n 50 for i in range(n): # Perform some heavy computation time.sleep(0.1) # Update the progress bar through the queue q.put_nowait(i / n) return Done! ui.page(/) def main_page(): async def start_computation(): progressbar.visible True result await run.cpu_bound(heavy_computation, queue) ui.notify(result) progressbar.visible False # Create a queue to communicate with the heavy computation process queue Manager().Queue() # Update the progress bar on the main process ui.timer(0.1, callbacklambda: progressbar.set_value(queue.get() if not queue.empty() else progressbar.value)) # Create the UI ui.button(compute, on_clickstart_computation) progressbar ui.linear_progress(value0).props(instant-feedback) progressbar.visible False ui.run()2.1 耗时任务函数只负责算不碰 UIdef heavy_computation(q: Queue) - str: n 50 for i in range(n): time.sleep(0.1) q.put_nowait(i / n) return Done!该函数模拟了 50 步、每步耗时 0.1 秒的重型计算总耗时约 5 秒。每一步完成后调用q.put_nowait(i / n)把当前完成比例0.01.0非阻塞地写入队列。这里有一个 NiceGUI 进程池的硬性约束需要遵守详见下文源码分析传给run.cpu_bound的函数必须是模块级module-level函数参数与返回值都必须能被pickle序列化。因此原示例刻意把计算逻辑从页面闭包中剥离为顶层函数而不是在页面内定义嵌套函数或 lambda——这是示例代码最重要的设计意图之一。2.2 页面与异步事件处理器ui.page(/) def main_page(): async def start_computation(): progressbar.visible True result await run.cpu_bound(heavy_computation, queue) ui.notify(result) progressbar.visible Falsestart_computation是一个async 事件处理器由按钮点击触发核心动作只有三个显示进度条progressbar.visible Trueawait run.cpu_bound(...)把重活交给进程池await挂起协程但不阻塞事件循环其余客户端请求仍可正常响应任务完成后用ui.notify(result)弹出 Done! 通知并隐藏进度条。2.3 跨进程进度通道Manager().Queue()queue Manager().Queue()这是本示例的关键设计。run.cpu_bound在独立进程中执行heavy_computation而进度条属于主进程服务端的 UI 状态两者无法直接共享内存。multiprocessing.Manager().Queue()是一个进程安全的代理队列worker 进程通过put_nowait写入主进程通过get读出NiceGUI 由此实现了后台进程产生进度、前端消费进度的解耦。说明也可以改用multiprocessing.Queue()的全局实例但Manager().Queue()在 spawn 启动方式下更稳妥且无需额外的初始化器配合示例的选择具备良好的可移植性。2.4 前端轮询ui.timer驱动进度条ui.timer(0.1, callbacklambda: progressbar.set_value(queue.get() if not queue.empty() else progressbar.value))ui.timer(0.1, callback)每 0.1 秒执行一次回调具体实现见 nicegui/elements/timer.py它是按客户端注册的定时器。回调里队列非空取出最新进度queue.get()并set_value队列为空保持progressbar.value不变避免把进度回退。这样无论 worker 进程写得有多快前端都以固定节奏平滑推进进度条。10 Hz 的轮询频率在 5 秒的 50 步任务下体验流畅且开销极小。2.5 进度条元素与按钮ui.button(compute, on_clickstart_computation) progressbar ui.linear_progress(value0).props(instant-feedback) progressbar.visible Falseui.linear_progress(value0)初始进度为 0.props(instant-feedback)关闭 Quasar 的过渡动画进度更新立即呈现——这正是进度条实时跟随队列数据的关键初始visible False计算开始前不显示进度条避免空进度条干扰界面。三、源码纵深run.cpu_bound的进程池原理进度条背后真正的主角是 nicegui/run.py 中实现的run.cpu_bound。理解它的内部机制才能解释示例代码为什么要写成模块级函数 队列回传。3.1 两个内置执行器在 run.py 中NiceGUI 维护了两个全局执行器process_pool: ProcessPoolExecutor | None None thread_pool ThreadPoolExecutor()run.io_bound(callback, ...)把 I/O 密集任务网络请求、文件读写、无异步支持的数据库驱动提交给线程池见 run.pyrun.cpu_bound(callback, ...)把 CPU 密集任务提交给进程池见 run.py。官方文档 nicegui/llms.md 明确建议优先使用run.io_bound/run.cpu_bound而不是自行asyncio.to_thread()或手写run_in_executor因为 NiceGUI 统一管理池的创建与关闭。3.2 为什么 CPU 密集任务必须进独立进程Python 的 GIL全局解释器锁决定了在单进程内CPU 密集代码几乎无法通过线程获得并行加速。若直接在事件循环中执行整个应用会卡死若塞进线程池GIL 仍会让其他协程饿肚子。因此run.cpu_bound把函数连同参数pickle序列化后交给ProcessPoolExecutor在独立进程中执行return await _run(process_pool, safe_callback, callback, *args, **kwargs)见 run.py。这带来两个示例中必须遵守的约束源码 docstring 与 llms.md 均有说明函数必须是可 pickle 的请使用静态方法或模块级自由函数把数据作为简单参数传入、把结果作为返回值传出不要在函数内触碰 UI 或类属性传参和返回值同样要可 pickle本示例传入的Manager().Queue()恰好满足这一点跨进程通信因此成立。此外run.cpu_bound对子进程异常做了包装子进程中抛出的异常会被safe_callback捕获并转换为可 picklable 的SubprocessException见 run.py主进程侧await处会重新抛出保证报错信息类型、消息、堆栈完整透传。3.3 进程池的启动方式与注意事项run.py 提供了可配置的启动方式process_pool_start_method: Literal[spawn, fork, forkserver] | None NoneNone默认沿用平台默认。在 Linux/Docker 上 Python 3.13 之前默认是forkNiceGUI 会打印一次警告因为fork在线程化进程中不安全详见 run.py 的说明spawn推荐选项worker 不继承父进程状态跨平台行为一致。NiceGUI 4.0 将把默认值改为spawn必须在ui.run()之前设置例如from nicegui import run run.process_pool_start_method spawn见 llms.md。在 Windows 上只有spawn可用以及使用 spawn 时请确保计算函数与ui.run()都位于if __name__ __main__:保护之下避免子进程重复执行页面注册代码。四、进度条元素速查ui.linear_progress与ui.circular_progress示例使用的ui.linear_progress定义在 nicegui/elements/progress.py是对 QuasarQLinearProgress组件的封装。构造参数如下参数类型默认值说明valuefloat0.0当前进度取值范围 0.01.0sizestr \| NoneNone进度条高度None时自动取 20px显示数值标签或 4px不显示show_valueboolTrue是否在进度条中央显示百分比数值标签colorstr \| Noneprimary颜色支持 Quasar、Tailwind 或 CSS 颜色传None表示不设颜色当show_valueTrue时组件内部会在进度条中央放置一个绑定value的文本标签见 progress.py因此示例中配合.props(instant-feedback)使用数值标签会与进度条同步跳动形成直观的实时反馈。同文件还提供了环形进度条ui.circular_progressprogress.py参数为value、min默认 0.0、max默认 1.0、size默认xl、show_value、color并内置track-color: grey-4的轨道底色。当 UI 空间紧张时可把示例中的ui.linear_progress直接替换为ui.circular_progress(value0)轮询回调set_value的用法完全一致。五、用测试佐证run.cpu_bound的行为边界仓库测试 tests/test_run.py 对run.cpu_bound的边界行为做了系统性验证可作为理解示例行为边界的依据test_delayed_hello验证run.cpu_bound与run.io_bound都能在异步处理器中正常 await 并返回结果test_run_unpickable_exception_in_cpu_bound_callback与test_run_cpu_bound_function_which_raises_problematic_exception验证子进程内不可 pickle 的异常会被包装成SubprocessException安全传回主进程test_run_cpu_bound_survive_bad_function一个函数出错后进程池仍可继续执行后续正常任务BrokenProcessPool会被自动重建对应 run.py 的重建逻辑test_returns_none_when_app_is_stopping应用关闭或协程被取消时run.cpu_bound返回None而非结果这是 4.0 之前的中期行为4.0 将改为抛出CancelledErrortest_pool_uses_configured_start_method、test_fork_heads_up_warning等验证process_pool_start_method的配置生效与 fork 警告逻辑。这些测试从侧面印证了示例代码的稳健性设计进度回传通过队列独立于返回值进行即使run.cpu_bound因取消/关闭返回None进度条也不会因此卡死或异常。六、运行方式与扩展思路6.1 运行示例cd examples/progress python main.py浏览器访问http://localhost:8080点击compute按钮即可看到进度条从 0 推进到 100%随后弹出 Done! 通知默认端口可在ui.run(port...)中调整。6.2 从示例到生产的扩展建议进度粒度把heavy_computation中的n换成实际任务步数如文件数量、数据块数i / n即天然的单位进度取消机制可为按钮增加禁用状态button.disable()/button.enable()并在页面卸载时清理队列多任务并发每个任务使用独立Manager().Queue()或改为在队列中传递(job_id, progress)元组以区分并发任务结合run.io_bound若耗时主要来自 I/O如批量网络请求把run.cpu_bound替换为run.io_bound即可前端进度条代码无需改动环形进度将ui.linear_progress换成ui.circular_progress即可适配仪表盘类界面。七、小结这个不足 40 行的官方示例浓缩了 NiceGUI 处理重型任务的核心范式计算进程化run.cpu_bound 进度通道化Manager().Queue() 前端轮询化ui.timer 渲染即时化.props(instant-feedback)。掌握这四个环节你就可以为任意耗时操作提供平滑、不阻塞的实时进度反馈——这正是生产级 Web 应用体验的关键一环。深入阅读 nicegui/run.py、nicegui/elements/progress.py 与 tests/test_run.py可进一步理解 NiceGUI 进程池的完整设计与边界行为。【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表