接上节继续,今天来研究tauri的事件(event),假设老板提了个需求,希望能实时监控cpu、内存等性能指标,你会怎么做?
思路1:
后端Rust暴露1个command,前端js不停去轮询(参考前文:tauri学习(3)-前端调用Rust代码),即传统的pull模型。
思路2:
后端不停对外喷数据,谁需要谁拿(类似发布-订阅模型)。
理论上二种思路都可以,今天讨论的是第2种,通过tauri的事件触发与监听来实现,而且event机制不仅仅限制于前端与后端通讯,还可以在前端-前端,后端与后端(多窗口应用,窗口之间)交换数据)。
一、准备工作:
1.1、如何在Rust中获取到CPU等实时监控指标?
代码语言:javascript复制perf_monitor = "0.2.0"
这里借助了perf_monitor这个开源第3方库,基本用法如下:
代码语言:javascript复制use perf_monitor::cpu::{processor_numbers, ProcessStat, ThreadStat};
use perf_monitor::fd::fd_count_cur;
use perf_monitor::io::get_process_io_stats;
use perf_monitor::mem::get_process_memory_info;
/**
* 获取【cpu/内存/文件描述符数量/io】监控值
*/
fn monitor() -> Vec<String> {
// cpu
let core_num = processor_numbers().unwrap();
let mut stat_p = ProcessStat::cur().unwrap();
let mut stat_t = ThreadStat::cur().unwrap();
let usage_p = stat_p.cpu().unwrap() * 100f64;
let usage_t = stat_t.cpu().unwrap() * 100f64;
let mut monitor_message: Vec<String> = Vec::with_capacity(3);
monitor_message.push(format!(
"[CPU] core Number: {}, process usage: {:.2}%, current thread usage: {:.2}%",
core_num, usage_p, usage_t
));
// mem
let mem_info = get_process_memory_info().unwrap();
monitor_message.push(format!(
"[Memory] memory used: {} bytes, virtural memory used: {} bytes ",
mem_info.resident_set_size, mem_info.virtual_memory_size
));
// fd
let fd_num = fd_count_cur().unwrap();
monitor_message.push(format!("[FD] fd number: {}", fd_num));
// println!("[FD] fd number: {}", fd_num);
// io
let io_stat = get_process_io_stats().unwrap();
monitor_message.push(format!(
"[IO] io-in: {} bytes, io-out: {} bytes",
io_stat.read_bytes, io_stat.write_bytes
));
monitor_message
}
1.2 设计事件的消息体
另:为了获取系统时间戳,从网上找了段代码
二、后端发送事件
2.1 发送事件代码
触发事件的核心就是emit方法(上图95行),事件名称可以随便取,但是要与前端监听指定的事件名保持一致。
说明一下:这里后端暴露了1个command,允许用户在前端通过按钮之类的,来触发后端吐数据(当然,大家也可以改成应用一启动,就直接开始监控cpu,无需前端触发)
另外,还演示了rust中的线程使用,创建1个独立线程来不停监控系统指标,然后1秒1次不停向外触发事件,这就带来另1个小问题,如果前端不停调用这个command,后端每次都会创建1个线程,容易引导其它问题,所以这里借助了1个全局变量来做辅助控制(当然,仅仅出于演示目的,应该有更优雅的做法)
2.2 暴露command
三、前端监听事件
代码语言:javascript复制import React from 'react';
import { invoke } from "@tauri-apps/api/tauri";
//监听事件
import { listen } from "@tauri-apps/api/event";
//用于格式化date
import format from 'date-fns/format';
import './index.css';
//用于取消监听
let unlisten: any = null
//事件的消息体
interface Payload {
message: Array<string>,
timestamp: number,
}
class Home extends React.Component {
//初始状态
state = {
message: [],
timestamp: "",
time: ""
}
//开始监听
start = () => {
invoke('init_process');
//防止重复监听
if (unlisten != null) {
console.log("already listen");
return;
}
const start_listen = async () => {
//注意这里的my-event名称,要与后端保持一致
return await listen<Payload>('my-event', (event) => {
const { message, timestamp } = event.payload;
console.log("message:", message,
"timestamp:", timestamp, "time:",
format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss.SSS'));
this.setState({ message, timestamp, "time": format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss.SSS') })
});
};
unlisten = start_listen();
}
//停止监听
stop = () => {
console.log("is_listening:", unlisten != null);
if (unlisten != null) {
unlisten.then((ok: any) => {
ok();
unlisten = null;
console.log("stop success");
}).catch((err: any) => {
console.log("stop fail", err);
})
}
}
render() {
return (
<div>
<button onClick={() => this.start()}>start</button>
<button onClick={() => this.stop()}>stop</button><br />
<h4>{this.state.time}</h4>
<div >
{
this.state.message.map((item, index) => {
return (<span className="monitor" key={`${this.state.timestamp}_${index}`}> {item}</span>)
})
}
</div>
</div >
)
}
}
export default Home
核心部分都加了注释,应该不难看懂,运行效果如下:
代码示例:
https://github.com/yjmyzz/tauri-visited-solution/tree/event
参考文章:
https://tauri.app/v1/guides/features/events