Как синхронно и последовательно удалить папку на NTFS с помощью C#
Это:
Directory.Delete(dir, true);
Не синхронно.
В строке немедленного продолжения вы все еще можете манипулировать/читать каталог.
Например, это:
Directory.Delete(destinationDir, true);
Directory.CreateDirectory(destinationDir);
Thread.Sleep(1000);
Приводит к тому, что папка не существует. Delete работает асинхронно, CreateDirectory не создает, потому что он уже существует, затем delete фактически запускает и удаляет каталог.
Есть ли IO API, который даст мне согласованность?
Ответ с участием Thread.Sleep вызовет Zalgo. Мне нужно реальное решение. пожалуйста.
3 ответов:
После некоторого тестирования в 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 (см. ссылочный Источник).
- - - EDIT - - -
После тестирования в C#, это, кажется, работает! Единственная проблема в том, что в самый первый раз (с момента запуска приложения) вы вызываете функцию P/Invoked
SHFileOperation(), она возвращает значение 2, которое эквивалентноERROR_FILE_NOT_FOUND. Но если вы выполните его снова, он вернет 0 (успех).Нативные методы.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);Надеюсь, это поможет!
В противном случае вы могли бы использовать A FileWatcher, чтобы реагировать на удаление каталога, но это кажется излишним.
Как упоминалось другими, похоже, что платформа .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);Никаких реальных сюрпризов там нет, но, по крайней мере, это рабочее решение, которое не требует вызова API C++ из управляемого кода.
Comments