Gradio.Net 开发指南 -- 在 Blocks 中管理状态

📅 2026/8/4 8:17:28 👁️ 阅读次数 📝 编程学习
Gradio.Net 开发指南 -- 在 Blocks 中管理状态

目录

在 Blocks 中管理状态(State In Blocks)

全局状态(Global State)

会话状态(Session State)

无法深拷贝对象:按会话隔离实例

浏览器状态(Browser State)

总结


上一篇

Gradio.Net (https://github.com/feiyun0112/Gradio.Net)是一个开源的 .NET 库,它是 Gradio 的 .NET 移植版本,允许你为机器学习模型、API 或任何 C# 函数快速构建演示或 Web 应用程序,无需任何 JavaScript、CSS 或 Web 开发经验!

在 Blocks 中管理状态(State In Blocks)

当你使用gr.Blocks()构建应用时,通常会遇到“状态”问题:

  • 有些值需要在所有用户之间共享(例如访问计数);

  • 有些值只需要在单个用户会话内保留(例如购物车、聊天历史);

  • 有些值希望刷新页面后仍然保留(例如用户名、偏好设置)。

在 Gradio / Gradio.Net 中,常见有三种状态管理方式:

  • 全局状态(Global State):应用运行期间,所有用户共享。

  • 会话状态(Session State):仅对当前用户会话有效,刷新页面会重置。

  • 浏览器状态(Browser State):保存在浏览器 ‎localStorage,关闭/刷新后仍可恢复。

全局状态(Global State)

全局状态最简单:把变量定义在函数外部即可。下面示例里,visitorCount在所有用户之间共享。

using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; var visitorCount = 0; string IncrementCounter() { visitorCount += 1; return visitorCount.ToString(); } using var demo = gr.Blocks(); var number = gr.Textbox(label: "Total Visitors", value: "Counting..."); _ = gr.On( triggers: (List<EventListener>?)null, fn: IncrementCounter, outputs: number ); await demo.Launch();

代码

如果你不希望值在不同用户之间共享,请不要放在全局变量里,而应使用会话状态。

会话状态(Session State)

会话状态会在同一页面会话内持续保留,但不会跨用户共享;用户刷新页面后会重置。

使用步骤:

  1. 创建 ‎gr.State()(可传默认值)。

  2. 在事件监听里把该 ‎State作为输入和输出。

  3. 在函数参数和返回值中处理该状态。

下面是购物车示例:

using Gradio.Net; using Gradio.Net.Components; List<string> AddItems(List<string>? newItems, List<string>? previousCart) { var cart = new List<string>(previousCart ?? new()); if (newItems != null) { cart.AddRange(newItems); } return cart; } double CartSize(List<string>? cart) { return (cart ?? new List<string>()).Count; } using var demo = gr.Blocks(); var cart = gr.State(value: new List<string>()); var itemsToAdd = gr.CheckboxGroup(new[] { "Cereal", "Milk", "Orange Juice", "Water" }); var addButton = gr.Button("Add Items"); _ = addButton.Click( fn: AddItems, inputs: new object[] { itemsToAdd, cart }, outputs: cart ); var cartSize = gr.Number(label: "Cart Size"); _ = cart.Change( fn: CartSize, inputs: cart, outputs: cartSize ); await demo.Launch();

你可以把gr.State理解为一个“不可见组件”:前端不直接显示,但可以参与事件计算。

说明:gr.State的值需要可深拷贝(deepcopy-able)。如果状态对象本身无法深拷贝,请使用“按会话哈希维护全局字典”的方案。

无法深拷贝对象:按会话隔离实例

当对象(例如包含锁)无法深拷贝时,可以读取request.SessionHash,并在服务端用全局字典保存“每个用户会话”的实例。

using System.Collections.Concurrent; using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Core; using Gradio.Net.Events; var instances = new ConcurrentDictionary<string, NonDeepCopyable>(); string InitializeInstance(Request request) { if (string.IsNullOrWhiteSpace(request.SessionHash)) { return "Error: session hash missing"; } instances[request.SessionHash] = new NonDeepCopyable(); return "Session initialized!"; } double IncrementCounter(Request request) { if (string.IsNullOrWhiteSpace(request.SessionHash)) { return -1; } if (instances.TryGetValue(request.SessionHash, out var instance)) { return instance.Increment(); } return -1; } using var demo = gr.Blocks(); var output = gr.Textbox(label: "Status"); var counter = gr.Number(label: "Counter Value"); var incrementBtn = gr.Button("Increment Counter"); _ = gr.On( triggers: (List<EventListener>?)null, fn: InitializeInstance, outputs: output ); _ = incrementBtn.Click( fn: IncrementCounter, outputs: counter ); await demo.Launch(); class NonDeepCopyable { private readonly object _lockObj = new(); private int _counter; public int Increment() { lock (_lockObj) { _counter += 1; return _counter; } } }

浏览器状态(Browser State)

浏览器状态会写入本地localStorage,适合保存用户偏好、账号输入等信息。

using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; var random = new Random(); string GenerateToken(int len) { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string(Enumerable.Range(0, len).Select(_ => chars[random.Next(chars.Length)]).ToArray()); } object[] GenerateRandomly() { var username = GenerateToken(10); var password = GenerateToken(10); return new object[] { username, password }; } object[] LoadFromLocalStorage(object? savedValues) { if (savedValues is System.Collections.IList list && list.Count >= 2) { return new object[] { list[0]?.ToString() ?? string.Empty, list[1]?.ToString() ?? string.Empty }; } return new object[] { string.Empty, string.Empty }; } object SaveToLocalStorage(string username, string password) { return new object[] { username, password }; } Markdown ShowSavedMessage() { var timestamp = DateTime.Now.ToString("hh:mm:ss tt"); return gr.Markdown($"✅ Saved to local storage at {timestamp}", visible: true); } using var demo = gr.Blocks(); _ = gr.Markdown("Your Username and Password will get saved in the browser's local storage. If you refresh the page, the values will be retained."); var username = gr.Textbox(label: "Username"); var password = gr.Textbox(label: "Password", type: "password"); var btn = gr.Button("Generate Randomly"); var localStorage = gr.BrowserState(defaultValue: new[] { "", "" }); var savedMessage = gr.Markdown("✅ Saved to local storage", visible: false); _ = btn.Click( fn: GenerateRandomly, outputs: new object[] { username, password } ); _ = gr.On( triggers: (List<EventListener>?)null, fn: LoadFromLocalStorage, inputs: localStorage, outputs: new object[] { username, password } ); _ = username.Change( fn: SaveToLocalStorage, inputs: new object[] { username, password }, outputs: localStorage ); _ = password.Change( fn: SaveToLocalStorage, inputs: new object[] { username, password }, outputs: localStorage ); _ = localStorage.Change( fn: ShowSavedMessage, outputs: savedMessage ); await demo.Launch();

注意:gr.BrowserState的值保存在浏览器中。若 Gradio 应用重启,默认随机storage_key/secret会变化,历史值可能无法继续读取。要跨重启读取,请固定storage_keysecret,并确保在可信应用场景中使用。

总结

这一章你学会了三种状态管理方式:

  • 全局状态:适合跨用户共享的运行期数据。

  • 会话状态:适合单用户会话内的临时数据。

  • 浏览器状态:适合需要在刷新后保留的前端数据。

引入地址