如何使用 C# 同步一致地删除 NTFS 上的文件夹
How to synchronously and consistently delete a folder on NTFS with C#
这个:
Directory.Delete(dir, true);
不同步。
在紧接着的行中,您仍然可以manipulate/read目录。
例如,这个:
Directory.Delete(destinationDir, true);
Directory.CreateDirectory(destinationDir);
Thread.Sleep(1000);
导致文件夹不存在。删除运行异步,CreateDirectory
不创建,因为它已经存在,然后删除实际上触发并删除目录。
是否有 IO API 可以让我保持一致性?
涉及 Thread.Sleep
的答案将调用 Zalgo。我想要一个真正的解决方案。
这里有一个 link 的人有同样的问题,首先重命名/移动目录的解决方案可能适合你。
否则,您可以使用 FileWatcher 对目录删除做出反应,但这感觉有点矫枉过正。
在 C++ 中进行一些测试后,似乎用于删除 files/directories 的本机 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()
函数,它将 return 值为 2,相当于 ERROR_FILE_NOT_FOUND
.但是如果你再次执行它,它将 return 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);
希望对您有所帮助!
正如其他人所提到的那样,.net Framework 似乎并未 运行 同步显示。在 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 的可行解决方案。
这个:
Directory.Delete(dir, true);
不同步。
在紧接着的行中,您仍然可以manipulate/read目录。
例如,这个:
Directory.Delete(destinationDir, true);
Directory.CreateDirectory(destinationDir);
Thread.Sleep(1000);
导致文件夹不存在。删除运行异步,CreateDirectory
不创建,因为它已经存在,然后删除实际上触发并删除目录。
是否有 IO API 可以让我保持一致性?
涉及 Thread.Sleep
的答案将调用 Zalgo。我想要一个真正的解决方案。
这里有一个 link 的人有同样的问题,首先重命名/移动目录的解决方案可能适合你。
否则,您可以使用 FileWatcher 对目录删除做出反应,但这感觉有点矫枉过正。
在 C++ 中进行一些测试后,似乎用于删除 files/directories 的本机 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()
函数,它将 return 值为 2,相当于 ERROR_FILE_NOT_FOUND
.但是如果你再次执行它,它将 return 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);
希望对您有所帮助!
正如其他人所提到的那样,.net Framework 似乎并未 运行 同步显示。在 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 的可行解决方案。