C++/Win32:如何等待挂起删除完成?

Mor*_*hai 31 c++ file-io winapi ntfs

解决:
*可行的解决方案:@sbi
*解释实际发生的事情:@Hans
*解释为什么OpenFile没有通过"DELETE PENDING":@Benjamin

问题:
我们的软件在很大程度上是专有脚本语言的解释器引擎.该脚本语言能够创建文件,处理文件,然后删除文件.这些都是单独的操作,并且在这些操作之间没有保持打开文件句柄.(即在文件创建过程中创建一个句柄,用于写入,然后关闭.在文件处理部分,一个单独的文件句柄打开文件,从中读取,并在EOF关闭.最后,删除使用:: DeleteFile它只使用文件名,而不是文件句柄.

最近我们开始意识到特定的宏(脚本)有时无法在随后的某个随机时间创建文件(即它在"创建,处理,删除"的前100次迭代中成功,但是当它到来时回到创建它一百零一次,Windows回复"拒绝访问").

深入研究这个问题,我编写了一个非常简单的程序,它循环遍历这样的事情:

while (true) {
    HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
                               NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return OpenFailed;

    const DWORD dwWrite = strlen(pszFilename);
    DWORD dwWritten;

    if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
        return WriteFailed;

    if (!CloseHandle(hFile))
        return CloseFailed;

    if (!DeleteFileA(pszFilename))
        return DeleteFailed;
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这直接针对Win32 API,非常简单.我创建一个文件,写入它,关闭句柄,删除它,冲洗,重复...

但是在某些地方,我会在CreateFile()调用期间收到Access Denied(5)错误.看看sysinternal的ProcessMonitor,我可以看到底层的问题是当我试图再次创建它时,文件上有一个挂起的删除.

问题:
*有没有办法等待删除完成?
*有没有办法检测文件是否正在等待删除?

我们通过HFILE上的WaitForSingleObject()尝试了第一个选项.但是,在WaitForSingleObject执行之前,HFILE始终处于关闭状态,因此WaitForSingleObject始终返回WAIT_FAILED.显然,试图等待关闭的句柄不起作用.

我可以等待文件存在的文件夹的更改通知.但是,这似乎是一个非常开销密集的kludge只是偶尔会出现问题(也就是说:在我的Win7 x64 E6600 PC的测试中,它通常会失败迭代12000+ - 在其他机器上,它可以在迭代7或15或56或永远不会发生.

我无法识别任何明确允许此以太的CreateFile()参数.无论CreateFile有什么参数,当文件待删除时打开文件进行任何访问都是不行的.由于我可以在XP盒子和x64 Win7盒子上看到这种行为,我很确定这是微软的"按照预期"的核心NTFS行为.所以我需要一个允许操作系统在我尝试继续之前完成删除的解决方案,最好是不必要地占用CPU周期,并且没有观察该文件所在文件夹的极端开销(如果可能的话).

感谢您抽出宝贵时间阅读并发布回复.澄清问题欢迎!

[1]是的,这个循环返回写入失败或无法关闭哪个泄漏,但由于这是一个简单的控制台测试应用程序,应用程序本身退出,Windows保证所有句柄在操作系统关闭时完成.所以这里没有泄漏.

bool DeleteFileNowA(const char * pszFilename)
{
    // Determine the path in which to store the temp filename
    char szPath[MAX_PATH];
    strcpy(szPath, pszFilename);
    PathRemoveFileSpecA(szPath);

    // Generate a guaranteed to be unique temporary filename to house the pending delete
    char szTempName[MAX_PATH];
    if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
        return false;

    // Move the real file to the dummy filename
    if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
        return false;

    // Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
    if (!DeleteFileA(szTempName))
        return false;

    return true;
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 20

Windows中还有其他需要该文件的进程.搜索索引器是一个明显的候选者.或者病毒扫描程序.他们将打开文件以进行完全共享,包括FILE_SHARE_DELETE,以便其他进程不会受到打开文件的严重影响.

这通常很有效,除非您以高速率创建/写入/删除.删除将成功,但文件系统不能从文件系统中消失,直到它的最后一个句柄关闭.由搜索索引器持有的句柄.试图打开该挂起删除文件的任何程序都将被错误5打耳机.

这是多任务操作系统上的一般问题,您无法知道其他进程可能想要弄乱您的文件.您的使用模式似乎不寻常,请先查看.解决方法是捕获错误,睡眠并再试一次.或者使用SHFileOperation()将文件移动到回收站中.

  • 公平地说,这是一个特定于windows的问题,而不是一般的os问题.其他操作系统通过允许等效的DeleteFile删除名称而不删除文件来轻松处理此问题,因此索引器不会阻止文件被删除.UNIX系统就是这种行为的一个例子.问题的存在是因为Win32(而不是NTFS)需要每个打开文件的名称. (7认同)

sbi*_*sbi 15

为什么不首先重命名要删除的文件,然后将其删除?

使用GetTempFileName()获得一个唯一的名称,然后用MoveFile()重命名文件.然后删除重命名的文件.如果实际缺失确实是异步的,并且可能与创建同一文件的冲突(如你的测试似乎表明),这应该解决的问题.

编辑:当然,如果您的分析是正确的,并且文件操作有点异步,这可能会引入您在重命名之前尝试删除文件的问题.但是你可以随时继续尝试在后台线程中删除.

编辑#2:如果汉斯是对的(我倾向于相信他的分析),那么移动可能不会真正有用,因为你可能无法实际重命名由另一个进程打开的文件.(但是你可能,我不知道这一点.)如果确实如此,我唯一能想到的另一种方法就是"继续努力".你必须等待几毫秒然后重试.如果没有帮助,请暂停超时.

  • +1.汉斯是对的,也许并不重要.您可以重命名打开的文件.这没有问题,因为文件句柄大多数与文件名无关.该名称仅在您从文件名创建文件句柄时才起作用.具有打开文件句柄的假设过程不会受到影响. (3认同)

小智 5

愚蠢的建议 - 因为它很少失败,如何简单地等待几毫秒失败并再次尝试?或者,如果延迟很重要,请切换到另一个文件名,以便稍后删除旧文件.