Как синхронно и последовательно удалить папку на NTFS с помощью C#



Это:



Directory.Delete(dir, true);


Не синхронно.



В строке немедленного продолжения вы все еще можете манипулировать/читать каталог.



Например, это:



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


Приводит к тому, что папка не существует. Delete работает асинхронно, CreateDirectory не создает, потому что он уже существует, затем delete фактически запускает и удаляет каталог.



Есть ли IO API, который даст мне согласованность?



Ответ с участием Thread.Sleep вызовет Zalgo. Мне нужно реальное решение. пожалуйста.

666   3  

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

    Ничего не найдено.