慎用 Tauri 读取文件
TL;DR
一句话,要么升级 v2,要么
吃屎
这篇文章真的是加急写出来的,因为 Tauri v1 的 readBinaryFile
API 太蠢了,我受不了了。
问题
这个 API 会有内存泄漏的问题,而且 v1 版本是避免不了的,举个例子就是读取 400M 左右的文件,进程会吃 18G 左右,读取完成后再慢慢清理回来。
在做测试的过程中,我一开始是直接读文件,读一半因为这个问题,程序崩溃了两回,后面我调整了写法才避免了崩溃,但吃内存的问题还是无法解决。
我查了 issue 才发现,这个问题是序列化导致的,Tauri 的 JS API 会将一切 Rust 端的数据序列化成 JSON 然后传回前端,这也包括了文件数据,我估计他是把文件同步读完,然后再开辟一份新内存区域去做转换,所以浪费了那么多空间。
速度慢的问题在开发环境特别明显,在生产环境会好不少。
解法
根据 issue 下面所说,如果你想彻底解决这个问题,要么升级 v2 后使用 tauri::ipc::Response
包装 Vec
,要么自己做一个协议去交换数据,不依赖 Tauri 提供的 NPM 包。
但 v1 升级到 v2 肯定又是一场恶战,我目前在 v1 的办法也只能暂缓爆内存的问题,就是限制并发量:
export const runConcurrency = async <T>(
tasks: Array<() => Promise<T>>,
concurrency: number = 3
): Promise<Array<T | Error>> => {
const results: Array<T | Error> = new Array(tasks.length);
let currentIndex = 0;
// 创建工作线程函数
const worker = async () => {
while (currentIndex < tasks.length) {
const taskIndex = currentIndex++;
const task = tasks[taskIndex];
try {
const result = await task();
results[taskIndex] = result;
} catch (err) {
results[taskIndex] = err as Error;
}
}
};
// 创建指定数量的工作线程
const workers = Array(Math.min(concurrency, tasks.length))
.fill(null)
.map(() => worker());
// 等待所有工作线程完成
await Promise.all(workers);
return results;
}
大概是这样,限制同时读取的文件数量,单个文件只能听天由命了。
其他解法(大概)
Claude 的方案是自己做一个 Tauri command 去读取文件,我也试过了,效果不明显,毕竟 command 的数据交换也是要通过 JS API 的,那也会序列化成 JSON:
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{command, generate_handler, Wry};
use tokio::fs;
use anyhow::Result;
#[command]
async fn read_file_binary(path: String) -> Result<Vec<u8>, String> {
match fs::read(&path).await {
Ok(data) => Ok(data),
Err(e) => Err(e.to_string())
}
}
pub fn init() -> TauriPlugin<Wry> {
Builder::new("file")
.invoke_handler(generate_handler![
read_file_binary,
])
.build()
}
参考
Subscribe to my newsletter
Read articles from Erioifpud directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by