如何异步获取线程中打开文件的file/seek位置以实现拷贝进度通知
实现文件拷贝进度的主线程同步(不修改
write_to_file) 核心思路:通过自定义Read包装器,在每次读取操作后累加已读字节数,并同步到主线程可访问的共享变量中,同时计算进度百分比。因为无法修改write_to_file的签名,我们让包装器实现Read trait,从而无缝替换原有的File实例。
修改后的完整代码
use std::io::{Read, Write, Seek, SeekFrom}; use std::fs; use std::sync::{Arc, Condvar, Mutex}; // 无法修改的原函数 fn write_to_file<R: Read, W: Write>(mut reader: R, mut writer: W) { let mut buf = vec![0; 1024]; let mut bytes_read = buf.capacity(); while bytes_read > 0 { bytes_read = reader.read(&mut buf).expect("failed to read from the file"); if bytes_read > 0 { writer.write_all(&buf[..bytes_read]).expect("failed to write to the file"); } } writer.flush().expect("failed to flush the writer"); } // 自定义Reader包装器:跟踪读取进度并同步到主线程 struct ProgressReader<R> { inner: R, progress: Arc<(Condvar, Mutex<(u64, u64)>)>, // (已读字节数, 总字节数) } impl<R: Read> Read for ProgressReader<R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { let bytes_read = self.inner.read(buf)?; if bytes_read > 0 { let (cv, mutex) = &*self.progress; let mut guard = mutex.lock().unwrap(); guard.0 += bytes_read as u64; // 每次更新后通知主线程 cv.notify_one(); } Ok(bytes_read) } } fn foo(s: Arc<(Condvar, Mutex<(u64, u64)>)>) { let src_path = "~/a.txt"; let dst_path = "~/b.txt"; let mut src_file = fs::File::open(src_path).expect("unable to open source file"); // 获取源文件总大小 let total_size = src_file.seek(SeekFrom::End(0)).expect("failed to get file size"); src_file.seek(SeekFrom::Start(0)).expect("failed to reset file pointer"); let dst_file = fs::File::create(dst_path).expect("unable to create destination file"); // 初始化进度:已读0,总大小total_size { let mut guard = s.1.lock().unwrap(); *guard = (0, total_size); s.0.notify_one(); } // 使用自定义ProgressReader代替原始File let progress_reader = ProgressReader { inner: src_file, progress: s.clone(), }; // 调用原write_to_file函数 write_to_file(progress_reader, dst_file); // 拷贝完成后设置进度为100%(确保主线程退出循环) { let mut guard = s.1.lock().unwrap(); guard.0 = guard.1; s.0.notify_one(); } } fn main() { // 共享状态:(条件变量, Mutex<(已读字节数, 总字节数)>) let s = Arc::new((Condvar::new(), Mutex::new((0, 0)))); let sa = Arc::clone(&s); let jh = std::thread::spawn(move || { foo(sa); }); let mut prev_progress = 0; let (cv, mutex) = &*s; loop { let mut guard = mutex.lock().unwrap(); // 等待进度更新或总大小初始化 while guard.1 == 0 || (guard.0 * 100 / guard.1) == prev_progress { guard = cv.wait(guard).unwrap(); } let current_progress = (guard.0 * 100 / guard.1) as i32; prev_progress = current_progress; println!("拷贝进度:{}%", current_progress); if current_progress == 100 { break; } } jh.join().expect("failed to join worker thread"); }
关键部分说明
ProgressReader包装器:
- 包装原始Read实例,每次
read调用后累加已读字节数 - 通过共享的Condvar通知主线程进度变化,完全适配
write_to_file的参数要求
- 包装原始Read实例,每次
进度计算逻辑:
- 先通过
Seek获取源文件总大小,再重置文件指针到开头 - 用已读字节数除以总大小计算百分比,避免直接操作File的seek位置(防止干扰正常读取流程)
- 先通过
线程同步:
- 使用Condvar让主线程仅在进度更新时被唤醒,避免轮询浪费资源
- 拷贝完成后强制设置进度为100%,确保主线程能正确退出循环
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

