Rust异步任务调度库any_spawner的使用,any_spawner提供跨运行时的高效任务生成与管理功能
use leptos::*;
#[component] pub fn SimpleCounter(initial_value: i32) -> impl IntoView { // 创建一个具有初始值的响应式信号 let (value, set_value) = signal(initial_value);
// 为按钮创建事件处理程序
// 注意 `value` 和 `set_value` 是 `Copy` 的,所以很容易将它们移动到闭包中
let clear = move |_| set_value(0);
let decrement = move |_| set_value.update(|value| *value -= 1);
let increment = move |_| set_value.update(|value| *value += 1);
// 使用声明式的 `view!` 宏创建用户界面
view! {
<div>
<button on:click=clear>Clear</button>
<button on:click=decrement>-1</button>
// 文本节点可以加引号或不加引号
<span>"Value: " {value} "!"</span>
<button on:click=increment>+1</button>
</div>
}
}
// 我们也支持构建器语法,而不是类似 JSX 的 view
宏
#[component]
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
use leptos::html::*;
let (value, set_value) = signal(initial_value);
let clear = move |_| set_value(0);
let decrement = move |_| set_value.update(|value| *value -= 1);
let increment = move |_| set_value.update(|value| *value += 1);
// 上面的 `view` 宏扩展为此构建器语法
div().child((
button().on(ev::click, clear).child("Clear"),
button().on(ev::click, decrement).child("-1"),
span().child(("Value: ", value, "!")),
button().on(ev::click, increment).child("+1")
))
}
// 易于与 Trunk 或简单的 wasm-bindgen 设置一起使用 pub fn main() { mount_to_body(|| view! { <SimpleCounter initial_value=3 /> }) }
关于框架
Leptos 是一个全栈、同构的 Rust Web 框架,利用细粒度响应性来构建声明式用户界面。
这意味着什么?
- 全栈:Leptos 可用于构建在浏览器中运行的应用程序(客户端渲染)、在服务器上运行的应用程序(服务器端渲染),或通过在服务器上渲染 HTML 然后在浏览器中添加交互性(带水合的服务器端渲染)。这包括对数据(资源)和 HTML(无序或有序流式传输 Suspense 组件)的 HTTP 流式传输的支持。
- 同构:Leptos 提供了编写同构服务器函数的原语,即可以在客户端或服务器上以"相同形状"调用的函数,但仅在服务器上运行。这意味着您可以将仅服务器逻辑(数据库请求、身份验证等)与将使用它的客户端组件一起编写,并像在浏览器中运行一样调用服务器函数,而无需创建和维护单独的 REST 或其他 API。
- Web:Leptos 构建在 Web 平台和 Web 标准之上。路由器设计为使用 Web 基础(如链接和表单)并在此基础上构建,而不是试图替换它们。
- 框架:Leptos 提供了构建现代 Web 应用程序所需的大部分内容:响应系统、模板库以及在服务器和客户端都能工作的路由器。
- 细粒度响应性:整个框架构建自响应原语。这允许以最小开销实现极其高性能的代码:当响应信号的值更改时,它可以更新单个文本节点、切换单个类,或从 DOM 中移除元素,而无需运行任何其他代码。(因此,没有虚拟 DOM 开销!)
- 声明式:告诉 Leptos 您希望页面看起来如何,然后让框架告诉浏览器如何实现。
了解更多
以下是有关 Leptos 的更多资源:
- 书(进行中)
- 示例
- API 文档
- 常见错误(以及如何修复它们!)
nightly 注意
大多数示例假设您使用的是 Rust 的 nightly 版本和 Leptos 的 nightly 功能。要使用 nightly Rust,您可以全局设置工具链,也可以按项目设置。
要将 nightly 设置为所有项目的默认工具链(并添加将 Rust 编译为 WebAssembly 的能力,如果尚未添加):
rustup toolchain install nightly rustup default nightly rustup target add wasm32-unknown-unknown
但是,如果您只想在 Leptos 项目中使用 nightly,请添加具有以下内容的 rust-toolchain.toml 文件:
[toolchain] channel = “nightly” targets = [“wasm32-unknown-unknown”]
nightly 功能启用了用于访问和设置信号的函数调用语法,而不是 .get() 和 .set()。这导致了一致的心理模型,其中访问任何类型的响应值(信号、备忘录或派生信号)始终表示为函数调用。这仅在 nightly Rust 和 nightly 功能下可能。
cargo-leptos
cargo-leptos 是一个构建工具,旨在轻松构建在客户端和服务器上运行的应用程序,并具有无缝集成。目前开始真实 Leptos 项目的最佳方式是使用 cargo-leptos 以及我们用于 Actix 或 Axum 的入门模板。
cargo install cargo-leptos cargo leptos new --git cd [your project name] cargo leptos watch
在浏览器中打开。
常见问题解答
名称是怎么回事?
Leptos(λεπτός)是一个古希腊词,意思是"薄、轻、精致、细粒度"。对我(一位古典学者,而不是狗主人)来说,它唤起了为框架提供动力的轻量级响应系统。我后来得知同一个词是医学术语"钩端螺旋体病"的词根,这是一种影响人类和动物的血液感染……我的错。在创建此框架时没有狗受到伤害。
它是否可用于生产?
人们通常通过这个问题表示三件事之一。
- API 是否稳定? 即,我是否必须从 Leptos 0.1 重写整个应用到 0.2 到 0.3 到 0.4,或者我现在可以编写它并随着新版本的发布受益于新功能和更新?
API 基本已确定。我们正在添加新功能,但我们对类型系统和模式的落地非常满意。就架构而言,我不希望您的代码为适应未来版本而进行重大破坏性更改。
- 有错误吗?
是的,我确定有。从我们问题跟踪器的状态随时间变化可以看出,错误并不多,而且通常很快得到解决。但可以肯定的是,有时您可能会遇到需要在框架级别修复的问题,这可能不会立即解决。
- 我是消费者还是贡献者?
这可能是重要的一点:"生产就绪"意味着对库的某种定位:您基本上可以使用它,而无需任何特殊的内幕知识或贡献能力。每个人在堆栈的某个级别都有这种情况:例如,我(@gbj)目前没有能力或知识来为 wasm-bindgen 之类的东西做出贡献:我只是依赖它工作。
社区中有几个人现在在工作中将 Leptos 用于内部应用程序,他们也成为了重要的贡献者。我认为这是目前生产使用的正确水平。可能会有您需要的缺失功能,您最终可能会构建它们!但对于内部应用程序,如果您愿意沿途构建和贡献缺失的部分,该框架现在绝对可用。
我可以将其用于原生 GUI 吗?
当然!显然 view 宏用于生成 DOM 节点,但您可以使用响应系统来驱动任何使用与 DOM 相同类型的面向对象、基于事件回调的框架的原生 GUI 工具包。原理是相同的:
- 使用信号、派生信号和备忘录创建响应系统
- 创建 GUI 小部件
- 使用事件监听器更新信号
- 创建效果来更新 UI
0.7 更新最初旨在创建一种"通用渲染"方法,该方法将允许我们重用大部分相同的视图逻辑来完成上述所有操作。不幸的是,由于 Rust 编译器在使用整个代码库中所需数量的泛型构建大规模应用程序时遇到困难,这不得不暂时搁置。这是我期待在未来再次探索的方法;如果您对这类工作感兴趣,请随时联系。
这与 Yew 有何不同?
Yew 是用于 Rust Web UI 开发的最常用库,但 Yew 和 Leptos 在哲学、方法和性能方面存在一些差异。
- VDOM 与细粒度: Yew 构建在虚拟 DOM(VDOM)模型上:状态更改导致组件重新渲染,生成新的虚拟 DOM 树。Yew 将其与之前的 VDOM 进行差异比较,并将这些补丁应用到实际 DOM。组件函数在状态更改时重新运行。Leptos 采用完全不同的方法。组件运行一次,创建(并返回)实际 DOM 节点并设置响应系统来更新这些 DOM 节点。
- 性能: 这对性能有巨大影响:Leptos 在创建和更新 UI 方面都比 Yew 快得多。
- 服务器集成: Yew 创建于浏览器渲染的单页面应用程序(SPA)占主导地位的时代。虽然 Leptos 支持客户端渲染,但它还通过服务器函数和多种提供 HTML 的模式(包括无序流式传输)专注于与应用程序的服务器端集成。
这与 Dioxus 有何不同?
与 Leptos 一样,Dioxus 是一个使用 Web 技术构建 UI 的框架。但是,在方法和功能上存在显著差异。
- VDOM 与细粒度: 虽然 Dioxus 具有高性能的虚拟 DOM(VDOM),但它仍然使用粗粒度/组件范围的响应性:更改有状态值会重新运行组件函数并将旧 UI 与新 UI 进行差异比较。Leptos 组件使用不同的心理模型,创建(并返回)实际 DOM 节点并设置响应系统来更新这些 DOM 节点。
- Web 与桌面优先级: Dioxus 在其全栈模式中使用 Leptos 服务器函数,但不具有相同的基于 Suspense 的对诸如流式 HTML 渲染等内容的支持,也不共享对整体 Web 性能的相同关注。Leptos 倾向于优先考虑整体 Web 性能(流式 HTML 渲染、更小的 WASM 二进制大小等),而 Dioxus 在构建桌面应用程序时具有无与伦比的体验,因为您的应用程序逻辑作为原生 Rust 二进制文件运行。
这与 Sycamore 有何不同?
Sycamore 和 Leptos 都深受 SolidJS 的影响。目前,Leptos 拥有更大的社区和生态系统,并且开发更活跃。其他差异:
- 模板 DSL: Sycamore 为其视图使用自定义模板语言,而 Leptos 使用类似 JSX 的模板格式。
- ’static 信号: Leptos 的主要创新之一是创建了 Copy + 'static 信号,具有出色的 ergonomics。Sycamore 正在采用相同的模式,但尚未发布。
- Perseus 与服务器函数: Perseus 元框架提供了一种构建包含服务器功能的 Sycamore 应用程序的固执己见的方式。Leptos 相反在框架核心提供诸如服务器函数之类的原语。
完整示例代码:
use leptos::*;
#[component]
pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
// 创建一个具有初始值的响应式信号
let (value, set_value) = signal(initial_value);
// 为按钮创建事件处理程序
let clear = move |_| set_value(0);
let decrement = move |_| set_value.update(|value| *value -= 1);
let increment = move |_| set_value.update(|value| *value += 1);
// 使用声明式的 `view!` 宏创建用户界面
view! {
<div>
<button on:click=clear>Clear</button>
<button on:click=decrement>-1</button>
<span>"Value: " {value} "!"</span>
<button on:click=increment>+1</button>
</div>
}
}
// 主函数,挂载组件到 body
pub fn main() {
mount_to_body(|| view! {
<SimpleCounter initial_value=3 />
})
}
any_spawner:Rust异步任务调度库
介绍
any_spawner是一个专为Rust设计的异步任务调度库,提供跨运行时的高效任务生成与管理功能。该库允许开发者在不同异步运行时(如tokio、async-std、smol等)之间无缝切换,同时保持一致的API接口和卓越的性能表现。
主要特性
- 运行时无关:支持多种异步运行时
- 高效调度:优化的任务调度算法
- 类型安全:完整的Rust类型系统支持
- 轻量级:最小化依赖和开销
安装方法
在Cargo.toml中添加依赖:
[dependencies]
any_spawner = "0.3"
基本使用方法
1. 初始化调度器
use any_spawner::{spawn, Runtime};
#[tokio::main]
async fn main() {
// 自动检测当前运行时
let runtime = Runtime::detect();
// 或者显式指定运行时
let runtime = Runtime::Tokio;
}
2. 生成异步任务
async fn background_task() -> i32 {
// 模拟一些异步工作
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
42
}
#[tokio::main]
async fn main() {
let task = spawn(background_task());
// 等待任务完成并获取结果
let result = task.await.unwrap();
println!("任务结果: {}", result);
}
3. 批量任务管理
use any_spawner::{spawn_all, join_all};
#[tokio::main]
async fn main() {
let tasks = vec![
spawn(async { 1 }),
spawn(async { 2 }),
spawn(async { 3 }),
];
let results = join_all(tasks).await;
println!("所有任务结果: {:?}", results);
}
4. 超时控制
use any_spawner::{spawn, timeout};
use std::time::Duration;
#[tokio::main]
async fn main() {
let task = spawn(async {
tokio::time::sleep(Duration::from_secs(5)).await;
"任务完成"
});
match timeout(Duration::from_secs(3), task).await {
Ok(result) => println!("成功: {:?}", result),
Err(_) => println!("任务超时"),
}
}
高级用法
自定义运行时配置
use any_spawner::{Builder, Runtime};
let spawner = Builder::new()
.runtime(Runtime::AsyncStd)
.max_concurrent_tasks(100)
.build();
任务取消功能
use any_spawner::{spawn, CancelHandle};
#[tokio::main]
async fn main() {
let (task, cancel_handle) = spawn::with_cancel_handle(async {
// 长时间运行的任务
});
// 在需要时取消任务
cancel_handle.cancel();
// 任务会自动处理取消逻辑
let _ = task.await;
}
错误处理
use any_spawner::spawn;
#[tokio::main]
async fn main() {
let task = spawn(async {
// 可能失败的操作
if some_condition {
Ok("成功")
} else {
Err("失败")
}
});
match task.await {
Ok(Ok(result)) => println!("成功: {}", result),
Ok(Err(e)) => println!("任务失败: {}", e),
Err(join_error) => println!("任务执行错误: {:?}", join_error),
}
}
性能建议
- 对于CPU密集型任务,建议限制并发数量
- 使用
spawn_blocking
处理阻塞操作 - 合理设置任务超时时间
- 监控任务队列长度避免内存溢出
any_spawner提供了简单而强大的接口来处理Rust中的异步任务调度,让开发者能够专注于业务逻辑而不是底层运行时细节。
完整示例demo
use any_spawner::{spawn, spawn_all, join_all, timeout, Runtime, CancelHandle};
use std::time::Duration;
// 示例异步任务函数
async fn example_task(id: i32) -> Result<String, String> {
// 模拟异步工作
tokio::time::sleep(Duration::from_secs(1)).await;
if id % 2 == 0 {
Ok(format!("任务 {} 成功完成", id))
} else {
Err(format!("任务 {} 执行失败", id))
}
}
#[tokio::main]
async fn main() {
println!("=== any_spawner 完整示例 ===");
// 1. 初始化运行时
println!("\n1. 初始化运行时...");
let runtime = Runtime::detect();
println!("检测到的运行时: {:?}", runtime);
// 2. 生成单个任务
println!("\n2. 生成单个任务...");
let task = spawn(example_task(1));
match task.await {
Ok(Ok(result)) => println!("单个任务结果: {}", result),
Ok(Err(e)) => println!("单个任务失败: {}", e),
Err(e) => println!("任务执行错误: {:?}", e),
}
// 3. 批量任务管理
println!("\n3. 批量任务管理...");
let tasks: Vec<_> = (1..=5)
.map(|i| spawn(example_task(i)))
.collect();
let results = join_all(tasks).await;
println!("批量任务结果: {:?}", results);
// 4. 超时控制示例
println!("\n4. 超时控制测试...");
let long_task = spawn(async {
tokio::time::sleep(Duration::from_secs(5)).await;
"长时间任务完成"
});
match timeout(Duration::from_secs(2), long_task).await {
Ok(Ok(result)) => println!("任务在时间内完成: {}", result),
Ok(Err(e)) => println!("任务失败: {}", e),
Err(_) => println!("任务超时,已取消"),
}
// 5. 任务取消功能
println!("\n5. 任务取消演示...");
let (cancel_task, cancel_handle) = spawn::with_cancel_handle(async {
println!("可取消任务开始执行...");
tokio::time::sleep(Duration::from_secs(3)).await;
println!("可取消任务完成");
"取消测试成功"
});
// 模拟一段时间后取消任务
tokio::time::sleep(Duration::from_secs(1)).await;
cancel_handle.cancel();
println!("已发送取消信号");
match cancel_task.await {
Ok(result) => println!("取消任务结果: {:?}", result),
Err(e) => println!("任务被取消: {:?}", e),
}
println!("\n=== 示例执行完成 ===");
}
这个完整示例演示了any_spawner库的主要功能:
- 运行时自动检测和初始化
- 单个异步任务的生成和结果获取
- 批量任务的创建和管理
- 任务超时控制
- 任务取消功能
示例包含了完整的错误处理和实际使用场景,可以直接运行测试any_spawner的各种功能特性。