std::filesystem::copy 不能直接用于异步拷贝,因其同步阻塞且无进度回调;应改用 std::jthread 分块读写+sleep让权,并通过线程安全机制通知UI更新进度。

为什么不能直接用 std::filesystem::copy 做异步拷贝
因为 std::filesystem::copy 是同步阻塞的,哪怕你把它丢进 std::thread 或 std::async,底层 read/write 依然会阻塞线程,尤其在大文件、慢存储(如机械硬盘、网络挂载盘)上,线程可能长时间卡在系统调用里,导致线程池饥饿或响应延迟。更关键的是:它不提供进度回调,UI 线程没法实时更新进度条。
用 std::jthread + 分块读写 + std::chrono::milliseconds 控制节奏
核心思路是手动分块搬运数据,并在每块之间主动让出控制权,避免单次系统调用耗时过长。这不是“操作系统级异步 I/O”,但对 UI 友好、可中断、可报告进度。
- 每次只读
8192字节(可调),用std::vector避免反复分配buffer(8192) - 用
std::ifstream和std::ofstream的read()/write(),它们支持二进制流且不依赖 locale - 每写完一块后调用
std::this_thread::sleep_for(std::chrono::milliseconds(1)),给 UI 线程留出调度机会(实测 1ms 足够,太小没意义,太大卡顿) - 必须检查
ifs.gcount(),不是所有 read 都能读满;ofs.write()后也要检查!ofs
示例片段:
std::jthread copy_thread([&](std::stop_token stoken) {
std::ifstream ifs(src, std::ios::binary);
std::ofstream ofs(dst, std::ios::binary);
std::vector<char> buf(8192);
while (ifs && !stoken.stop_requested()) {
ifs.read(buf.data(), buf.size());
auto n = ifs.gcount();
if (n > 0) {
ofs.write(buf.data(), n);
if (!ofs) break;
// 更新进度:n 已写入
progress_callback(static_cast<size_t>(n));
} else if (ifs.eof()) {
break;
}
std::this_thread::sleep_for(1ms); // 关键:让 UI 线程呼吸
}
});</size_t></char>
如何安全通知 UI 线程更新进度
不能在工作线程里直接调用 Qt 的 QWidget::update() 或 Win32 的 InvalidateRect() —— 多数 GUI 框架要求 UI 操作必须在主线程执行。最轻量的做法是用线程安全队列 + 主循环轮询,或借助框架原生机制:
- Qt 场景:用
QMetaObject::invokeMethod(..., Qt::QueuedConnection)投递信号 - Win32:用
PostMessage()发自定义消息,主线程GetMessage()处理 - 跨平台裸 C++:用
std::atomic_flag+std::queue+std::mutex缓存进度值,UI 线程每帧try_pop()
注意:回调函数本身不能持有长生命周期锁,否则会拖慢工作线程。
Windows 上真异步 I/O 的代价与取舍
若真要使用 CreateFile + ReadFileEx + WriteFileEx(IOCP),需面对:文件句柄必须带 FILE_FLAG_OVERLAPPED,缓冲区必须页对齐(_aligned_malloc),且每个操作需维护 OVERLAPPED 结构体和完成例程。实际收益有限:现代 SSD 下,分块同步读写 + 1ms 让渡已足够流畅;而 IOCP 在小文件场景反而因上下文切换开销更大。除非你在做专业备份工具或处理 TB 级 NAS 文件流,否则没必要。
真正容易被忽略的是错误恢复:断电、磁盘拔出、权限突变都会让 read() 返回 0 或抛异常,工作线程必须捕获 std::filesystem::filesystem_error 并通过 stop_token 优雅退出,否则 std::jthread 析构时会调用 std::terminate。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











