You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何异步获取线程中打开文件的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");
}

关键部分说明

  1. ProgressReader包装器:

    • 包装原始Read实例,每次read调用后累加已读字节数
    • 通过共享的Condvar通知主线程进度变化,完全适配write_to_file的参数要求
  2. 进度计算逻辑:

    • 先通过Seek获取源文件总大小,再重置文件指针到开头
    • 用已读字节数除以总大小计算百分比,避免直接操作File的seek位置(防止干扰正常读取流程)
  3. 线程同步:

    • 使用Condvar让主线程仅在进度更新时被唤醒,避免轮询浪费资源
    • 拷贝完成后强制设置进度为100%,确保主线程能正确退出循环

内容的提问来源于stack exchange,提问作者Harry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:46:11