接上节继续,今天来研究tauri的事件(event),假设老板提了个需求,希望能实时监控cpu、内存等性能指标,你会怎么做?
思路1:
后端Rust暴露1个command,前端js不停去轮询(参考前文:tauri学习(3)-前端调用Rust代码),即传统的pull模型。
思路2:
后端不停对外喷数据,谁需要谁拿(类似发布-订阅模型)。
理论上二种思路都可以,今天讨论的是第2种,通过tauri的事件触发与监听来实现,而且event机制不仅仅限制于前端与后端通讯,还可以在前端-前端,后端与后端(多窗口应用,窗口之间)交换数据)。
一、准备工作:
1.1、如何在Rust中获取到CPU等实时监控指标?
perf_monitor = "0.2.0"
这里借助了perf_monitor这个开源第3方库,基本用法如下:
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个线程来不停监控cpu之类的指标,然后1秒1次不停向外触发事件,这就带来另1个小问题,如果前端不停调用这个command,后端每次都会创建1个线程,容易引导其它问题,所以这里借助了1个全局变量来做辅助控制(当然,这里也只是演示目标,应该有更好的控制办法)
2.2 暴露command
三、前端监听事件
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