【问题标题】:How to synchronously and consistently delete a folder on NTFS with C#如何使用 C# 同步一致地删除 NTFS 上的文件夹
【发布时间】:2016-04-03 10:35:49
【问题描述】:

这个:

Directory.Delete(dir, true);

不是同步的。

在接下来的行中,您仍然可以操作/读取目录。

例如,这个:

Directory.Delete(destinationDir, true);
Directory.CreateDirectory(destinationDir);
Thread.Sleep(1000);

导致文件夹不存在。删除运行异步,CreateDirectory 不创建,因为它已经存在,然后删除实际触发并删除目录。

是否有可以让我保持一致性的 IO API?

涉及Thread.Sleep 的答案将调用 Zalgo。我想要一个真正的解决方案。

【问题讨论】:

  • Directory.Create()之前循环while(Directory.Exists(destinationDir));怎么样?
  • 我没有看到任何关于 Directory.Delete 的异步内容。
  • @VisualVincent 我不是在寻找变通方法,NTFS/.NET 肯定不能被破坏到没有真正的解决方案
  • @VisualVincent kernel32.dll DeleteFile 似乎解释了原因:DeleteFile 函数在关闭时标记要删除的文件。因此,在文件的最后一个句柄关闭之前不会删除文件。随后调用 CreateFile 以打开文件失败并返回 ERROR_ACCESS_DENIED。
  • @Yuval :在 C++ 中对此进行测试后,我可以确认 不是 的情况。之后我打电话给RemovedDirectory 然后CreateDirectory。这两个函数都成功了,当我检查文件夹时,它有一个新的创建和最新的修改日期。

标签: c# io ntfs


【解决方案1】:

正如其他人所提到的,.net 框架似乎没有同步运行。在 PowerShell 中对其进行测试表明 .Net 调用不会等待,因此这样的事情会产生类似的结果:

Remove-Item -Recurse -Force "C:\tempfolder"
New-Item -ItemType Directory "C:\tempfolder"

使用文件观察器(前面也提到过)将确保目录删除在创建完成之前完成:

var path = @"C:\tempfolder";
var watcher = new FileSystemWatcher(path);
watcher.Deleted += (sender, args) => Directory.CreateDirectory(args.FullPath);
Directory.Delete(path, true);

没有真正的惊喜,但至少它是一个有效的解决方案,不涉及从托管代码调用 C++ API。

【讨论】:

    【解决方案2】:

    在 C++ 中进行一些测试后,用于删除文件/目录的本机 Windows 函数似乎确实阻塞了。当涉及到删除功能未被阻止时,问题似乎出在 .NET 方面,因为 Directory.CreateDirectory() 似乎在 Directory.Delete() 完成之前被调用。

    这是我在 Win32 控制台应用程序中尝试的:

    printf("Press enter to begin...");
    while(getchar() != '\n');
    
    LPCSTR DeletePath = "C:\\test\\DeleteMe"; //The directory to delete.
    _SHFILEOPSTRUCTA* fileopt = new _SHFILEOPSTRUCTA();
    
    fileopt->hwnd = NULL;        //No window handle.
    fileopt->wFunc = FO_DELETE;  //Delete mode.
    fileopt->pFrom = DeletePath; //The directory to delete.
    fileopt->pTo = NULL;         //No target directory (this is only used when moving, copying, etc.).
    fileopt->fFlags = FOF_NO_UI; //Display no UI dialogs.
    
    int Success = SHFileOperationA(fileopt); //Remove the entire directory and all it's contents.
    bool Success2 = CreateDirectoryA(DeletePath, NULL); //Create a new directory.
    
    LPCSTR ReturnedValue = "False"; //I'm no C++ guru, so please don't hate. :)
    LPCSTR ReturnedValue2 = "False";
    if(Success == 0) { ReturnedValue = "True"; } //The SHFileOperation() returns 0 if it succeeds.
    if(Success2 == true) { ReturnedValue2 = "True"; }
    
    //Print the result of SHFileOperation().
    printf("Returned value: ");
    printf(ReturnedValue);
    printf("\n");
    
    //Print the result of CreateDirectory().
    printf("Returned value 2: ");
    printf(ReturnedValue2);
    printf("\n");
    
    //Continue.
    printf("Press enter to exit...");
    while(getchar() != '\n');
    

    第一次按 ENTER 后,在显示结果之前会有一小段延迟,之后查看文件夹时它是空的,有一个新的创建和最后修改的日期 - 这意味着它已被删除并以正确的顺序重新创建.

    因此,为了实现您想要的,我想您可以尝试创建自己的方法来调用 SHFileOperation(),因为问题似乎是 Directory.Delete() 方法在 .NET 代码中执行迭代本身(请参阅Reference Source)。


    --- 编辑 ---

    在 C# 中测试后,这似乎有效!唯一的问题是第一次(自应用程序启动后)调用 P/Invoked SHFileOperation() 函数时,它将返回值 2,相当于 ERROR_FILE_NOT_FOUND。但是如果你再次执行它会返回 0(成功)。

    NativeMethods.cs:

    需要进口:

    using System;
    using System.Runtime.InteropServices;
    

    其余代码:

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    public static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHFILEOPSTRUCT
    {
        public IntPtr hwnd;
        public FileFuncFlags wFunc;
    
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pFrom;
    
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pTo;
        public FILEOP_FLAGS fFlags;
    
        [MarshalAs(UnmanagedType.Bool)]
        public bool fAnyOperationsAborted;
        public IntPtr hNameMappings;
    
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszProgressTitle;
    }
    
    public enum FileFuncFlags : uint
    {
        FO_MOVE = 0x1,
        FO_COPY = 0x2,
        FO_DELETE = 0x3,
        FO_RENAME = 0x4
    }
    
    [Flags]
    public enum FILEOP_FLAGS : ushort
    {
        FOF_MULTIDESTFILES = 0x1,
        FOF_CONFIRMMOUSE = 0x2,
        /// <summary>
        /// Don't create progress/report
        /// </summary>
        FOF_SILENT = 0x4,
        FOF_RENAMEONCOLLISION = 0x8,
        /// <summary>
        /// Don't prompt the user.
        /// </summary>
        FOF_NOCONFIRMATION = 0x10,
        /// <summary>
        /// Fill in SHFILEOPSTRUCT.hNameMappings.
        /// Must be freed using SHFreeNameMappings
        /// </summary>
        FOF_WANTMAPPINGHANDLE = 0x20,
        FOF_ALLOWUNDO = 0x40,
        /// <summary>
        /// On *.*, do only files
        /// </summary>
        FOF_FILESONLY = 0x80,
        /// <summary>
        /// Don't show names of files
        /// </summary>
        FOF_SIMPLEPROGRESS = 0x100,
        /// <summary>
        /// Don't confirm making any needed dirs
        /// </summary>
        FOF_NOCONFIRMMKDIR = 0x200,
        /// <summary>
        /// Don't put up error UI
        /// </summary>
        FOF_NOERRORUI = 0x400,
        /// <summary>
        /// Dont copy NT file Security Attributes
        /// </summary>
        FOF_NOCOPYSECURITYATTRIBS = 0x800,
        /// <summary>
        /// Don't recurse into directories.
        /// </summary>
        FOF_NORECURSION = 0x1000,
        /// <summary>
        /// Don't operate on connected elements.
        /// </summary>
        FOF_NO_CONNECTED_ELEMENTS = 0x2000,
        /// <summary>
        /// During delete operation, 
        /// warn if nuking instead of recycling (partially overrides FOF_NOCONFIRMATION)
        /// </summary>
        FOF_WANTNUKEWARNING = 0x4000,
        /// <summary>
        /// Treat reparse points as objects, not containers
        /// </summary>
        FOF_NORECURSEREPARSE = 0x8000
    }
    

    其他地方:

    string DeletePath = "C:\\test\\DeleteMe";
    NativeMethods.SHFILEOPSTRUCT fileopt = new NativeMethods.SHFILEOPSTRUCT();
    
    fileopt.hwnd = IntPtr.Zero;
    fileopt.wFunc = NativeMethods.FileFuncFlags.FO_DELETE;
    fileopt.pFrom = DeletePath;
    fileopt.pTo = null;
    fileopt.fFlags = NativeMethods.FILEOP_FLAGS.FOF_SILENT | NativeMethods.FILEOP_FLAGS.FOF_NOCONFIRMATION |
                     NativeMethods.FILEOP_FLAGS.FOF_NOERRORUI | NativeMethods.FILEOP_FLAGS.FOF_NOCONFIRMMKDIR; //Equivalent of FOF_NO_UI.
    
    int Success = NativeMethods.SHFileOperation(ref fileopt);
    Directory.CreateDirectory(DeletePath);
    
    MessageBox.Show("Operation returned value: " + Success.ToString(), "Test", MessageBoxButtons.OK, MessageBoxIcon.Information);
    

    希望这会有所帮助!

    【讨论】:

    • @AndrewBullock :像你一样测试常规的IO.Directory.Delete()IO.Directory.CreateDirectory() 之后,它似乎也有效。我不知道您可能遇到什么问题,但是它确实会在删除旧目录后创建新目录而没有任何问题。
    • 或者,如果 P/Invocation 没有正确阻塞,您可能必须创建自己的 C++ 包装器来执行这些操作。
    【解决方案3】:

    这是一个有同样问题的人的link,首先重命名/移动目录的解决方案可能对你有用。

    否则您可以使用 FileWatcher 对目录删除做出反应,但这感觉有点矫枉过正。

    【讨论】:

    • FSW 也被证明在删除方面存在某些问题。
    • Move (MoveFile) 在处理这个问题上做得很好——它似乎在强制释放句柄(如果不是错误的话)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-26
    • 1970-01-01
    • 1970-01-01
    • 2011-03-21
    • 2020-12-13
    • 1970-01-01
    相关资源
    最近更新 更多