不能直接套用std::async+std::future::wait_for实现真正超时取消;必须采用协作式中断:分块i/o+轮询std::stop_Token,c++20的std::jthread是最优解。

std::async + std::future::wait_for 能直接套用吗?
不能。直接对 std::async 返回的 std::future 调用 wait_for 只能阻塞等待任务结束,但无法中断正在执行的系统调用(比如 copyFileEx 或 unlink)。一旦底层 I/O 卡住(如网络盘断连、权限挂起),任务就真卡死了,超时形同虚设。
真正可行的思路是:把耗时操作拆成可协作中断的片段,并在关键点轮询中断信号。C++20 的 std::jthread 和 std::stop_token 是目前最干净的解法。
- 用
std::jthread启动每个文件操作,传入std::stop_token - 在每次读/写/删除前检查
token.stop_requested() - 对
CopyFileEx(windows)或sendfile/read+write(linux)做分块处理,每 copy 64KB 就查一次 token - 不要依赖
std::future::wait_for做“超时控制”,它只管等待,不管取消
Windows 下 CopyFileEx 如何配合超时和取消?
CopyFileEx 本身支持回调函数和取消句柄,但它的取消是异步的——调用 CancelSynchronousIo 后仍需等待线程退出,且该函数只能取消当前线程发起的 I/O,不适用于线程池场景。
更稳的做法是不用它的原生取消,改用“协作式取消”:
立即学习“C++免费学习笔记(深入)”;
- 把
CopyFileEx改为手动循环:CreateFile→ 分块ReadFile+WriteFile→ 每次调用后检查stop_token - 用
SetFileCompletionNotificationModes配合 I/O 完成端口(IOCP)也能实现非阻塞+可取消,但复杂度陡增,小批量操作没必要 - 注意
CopyFileEx的lpProgressRoutine回调里不能直接 throw 或 longjmp,否则可能破坏系统状态 - 若必须用
CopyFileEx,至少把超时逻辑放在外层:启动后开一个std::jthread等待,超时则调用CancelSynchronousIo并CloseHandle源/目标文件句柄(有风险,慎用)
Linux 下用 sendfile 还是 read/write?怎么插中断点?
sendfile 性能好,但不支持用户态中断——它是一条内核原子路径,调用期间无法响应 stop_token。一旦源文件被其他进程锁住或 NFS 挂起,就彻底卡死。
所以生产环境建议退回到带缓冲的 read+write 循环:
- 用
std::vector<char> buf(64 * 1024)</char>做缓冲区,每次read(fd_in, buf.data(), buf.size()) - 读完立刻检查
token.stop_requested(),返回std::errc::operation_canceled - 写之前也检查一次,避免写一半被中断
-
read返回 0 表示 EOF;返回 -1 且errno == EINTR可重试;其他错误(如EACCES)直接报错 - 别用
splice——它和sendfile一样不可中断,且跨文件系统时会自动 fallback 到 copy,行为不一致
delete 操作怎么做到可超时、可取消?
std::Filesystem::remove 是同步阻塞的,遇到只读文件、挂载点或权限问题就会卡住,没有内置超时。更麻烦的是,它不接受 cancellation token。
解决方案不是“包装一层”,而是提前规避:
- 删前先用
std::filesystem::status检查类型和权限,跳过目录或只读项(或按策略抛错) - 对普通文件,用
unlinkat(AT_FDCWD, path.c_str(), 0)替代remove,它失败更快(尤其在 NFS 上) - 如果要删整个目录树,必须自己递归 + 每层检查
stop_token,不能依赖std::filesystem::remove_all - 注意:Windows 下
DeleteFile对正在被打开的文件会失败并返回ERROR_SHARING_VIOLATION,这不是卡住,是明确错误,应重试或跳过
真正的难点从来不在“怎么写超时”,而在于 I/O 操作本身的不可中断性——所有看似“加个 timeout 参数”的方案,底层都得靠分块+轮询+早检查。漏掉任意一处检查点,超时就失效。