状态管理
在 Tauri 应用中,你经常需要跟踪应用的当前状态或管理与其相关事物的生命周期。Tauri 提供了一种使用 Manager
API 管理应用状态的简单方法,并在调用命令时读取它。
¥In a Tauri application, you often need to keep track of the current state of your application or manage the lifecycle of things associated with it. Tauri provides an easy way to manage the state of your application using the Manager
API, and read it when commands are called.
以下是一个简单的例子:
¥Here is a simple example:
use tauri::{Builder, Manager};
struct AppData { welcome_message: &'static str,}
fn main() { Builder::default() .setup(|app| { app.manage(AppData { welcome_message: "Welcome to Tauri!", }); Ok(()) }) .run(tauri::generate_context!()) .unwrap();}
你稍后可以使用任何实现 Manager
特性的类型访问你的状态,例如 App
实例:
¥You can later access your state with any type that implements the Manager
trait, for example the App
instance:
let data = app.state::<AppData>();
有关更多信息,包括访问命令中的状态,请参阅 访问状态 部分。
¥For more info, including accessing state in commands, see the Accessing State section.
可变性
¥Mutability
在 Rust 中,你不能直接改变在多个线程之间共享的值,或者当所有权通过共享指针(例如 Arc
(或 Tauri 的 State
))控制时的值。这样做可能会导致数据争用(例如,同时发生两次写入)。
¥In Rust, you cannot directly mutate values which are shared between multiple threads or when ownership is controlled through a shared pointer such as Arc
(or Tauri’s State
). Doing so could cause data races (for example, two writes happening simultaneously).
要解决这个问题,你可以使用称为 内部可变性 的概念。例如,标准库的 Mutex
可用于封装你的状态。这允许你在需要修改值时锁定该值,并在完成后解锁它。
¥To work around this, you can use a concept known as interior mutability. For example, the standard library’s Mutex
can be used to wrap your state. This allows you to lock the value when you need to modify it, and unlock it when you are done.
use std::sync::Mutex;
use tauri::{Builder, Manager};
#[derive(Default)]struct AppState { counter: u32,}
fn main() { Builder::default() .setup(|app| { app.manage(Mutex::new(AppState::default())); Ok(()) }) .run(tauri::generate_context!()) .unwrap();}
现在可以通过锁定互斥锁来修改状态:
¥The state can now be modified by locking the mutex:
let state = app.state::<Mutex<AppState>>();
// Lock the mutex to get mutable access:let mut state = state.lock().unwrap();
// Modify the state:state.counter += 1;
在范围结束时,或者当 MutexGuard
以其他方式被删除时,互斥锁会自动解锁,以便应用的其他部分可以访问和改变其中的数据。
¥At the end of the scope, or when the MutexGuard
is otherwise dropped, the mutex is unlocked automatically so that other parts of your application can access and mutate the data within.
何时使用异步互斥锁
¥When to use an async mutex
引用 Tokio 文档,通常可以使用标准库的 Mutex
而不是 Tokio 提供的异步互斥锁:
¥To quote the Tokio documentation, it’s often fine to use the standard library’s Mutex
instead of an async mutex such as the one Tokio provides:
与普遍看法相反,在异步代码中使用标准库中的普通互斥锁是可以的,而且通常更受欢迎……异步互斥的主要用例是提供对 IO 资源(例如数据库连接)的共享可变访问。
¥Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code … The primary use case for the async mutex is to provide shared mutable access to IO resources such as a database connection.
最好完整阅读链接的文档,以了解两者之间的权衡。你需要异步互斥锁的一个原因是你需要在等待点之间保持 MutexGuard
。
¥It’s a good idea to read the linked documentation fully to understand the trade-offs between the two. One reason you would need an async mutex is if you need to hold the MutexGuard
across await points.
你需要 Arc
吗?
¥Do you need Arc
?
通常看到 Arc
在 Rust 中用于跨多个线程共享值的所有权(通常与 Mutex
配对,形式为 Arc<Mutex<T>>
)。但是,你不需要将 Arc
用于存储在 State
中的内容,因为 Tauri 会为你完成此操作。
¥It’s common to see Arc
used in Rust to share ownership of a value across multiple threads (usually paired with a Mutex
in the form of Arc<Mutex<T>>
). However, you don’t need to use Arc
for things stored in State
because Tauri will do this for you.
如果 State
的生命周期要求阻止你将状态移动到新线程,你可以改为将 AppHandle
移动到线程中,然后检索你的状态,如下面的“使用 Manager 特性访问状态”部分所示。对于此类用例,AppHandle
的克隆成本故意很低。
¥In case State
’s lifetime requirements prevent you from moving your state into a new thread you can instead move an AppHandle
into the thread and then retrieve your state as shown below in the “Access state with the Manager trait” section. AppHandle
s are deliberately cheap to clone for use-cases like this.
访问状态
¥Accessing State
在命令中访问状态
¥Access state in commands
#[tauri::command]fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 { let mut state = state.lock().unwrap(); state.counter += 1; state.counter}
有关命令的更多信息,请参阅 从前端调用 Rust。
¥For more information on commands, see Calling Rust from the Frontend.
异步命令
¥Async commands
如果你正在使用 async
命令并想要使用 Tokio 的异步 Mutex
,你可以以相同的方式进行设置并像这样访问状态:
¥If you are using async
commands and want to use Tokio’s async Mutex
, you can set it up the same way and access the state like this:
#[tauri::command]async fn increase_counter(state: State<'_, Mutex<AppState>>) -> Result<u32, ()> { let mut state = state.lock().await; state.counter += 1; Ok(state.counter)}
请注意,如果你使用异步命令,则返回类型必须为 Result
。
¥Note that the return type must be Result
if you use asynchronous commands.
使用 [Manager
] 特性访问状态
¥Access state with the Manager
trait
有时你可能需要访问命令之外的状态,例如在不同的线程中或在 on_window_event
等事件处理程序中。在这种情况下,你可以使用实现 Manager
特性(例如 AppHandle
)的类型的 state()
方法获取状态:
¥Sometimes you may need to access the state outside of commands, such as in a different thread or in an event handler like on_window_event
. In such cases, you can use the state()
method of types that implement the Manager
trait (such as the AppHandle
) to get the state:
use tauri::{Builder, GlobalWindowEvent, Manager};
#[derive(Default)]struct AppState { counter: u32,}
// In an event handler:fn on_window_event(event: GlobalWindowEvent) { // Get a handle to the app so we can get the global state. let app_handle = event.window().app_handle(); let state = app_handle.state::<Mutex<AppState>>();
// Lock the mutex to mutably access the state. let mut state = state.lock().unwrap(); state.counter += 1;}
fn main() { Builder::default() .setup(|app| { app.manage(Mutex::new(AppState::default())); Ok(()) }) .on_window_event(on_window_event) .run(tauri::generate_context!()) .unwrap();}
当你不能依赖命令注入时,此方法很有用。例如,如果你需要将状态移动到使用 AppHandle
更容易的线程中,或者你不在命令上下文中。
¥This method is useful when you cannot rely on command injection. For example, if you need to move the state into a thread where using an AppHandle
is easier, or if you are not in a command context.
不匹配的类型
¥Mismatching Types
:::caution 提醒
如果你对 State
参数使用了错误的类型,你将得到一个运行时恐慌而不是编译时错误。
¥If you use the wrong type for the State
parameter, you will get a runtime panic instead of compile time error.
例如,如果你使用 State<'_, AppState>
而不是 State<'_, Mutex<AppState>>
,则不会有任何使用该类型管理的状态。
¥For example, if you use State<'_, AppState>
instead of State<'_, Mutex<AppState>>
, there won’t be any state managed with that type.
:::
如果你愿意,你可以使用类型别名封装你的状态以防止此错误:
¥If you prefer, you can wrap your state with a type alias to prevent this mistake:
use std::sync::Mutex;
#[derive(Default)]struct AppStateInner { counter: u32,}
type AppState = Mutex<AppStateInner>;
但是,请确保按原样使用类型别名,并且不要再次将其封装在 Mutex
中,否则你将遇到相同的问题。
¥However, make sure to use the type alias as it is, and not wrap it in a Mutex
a second time, otherwise you will run into the same issue.
Tauri 中文网 - 粤ICP备13048890号