无法使用Directory.Delete(path,true)删除目录


383

我正在使用.NET 3.5,尝试使用以下命令递归删除目录:

Directory.Delete(myPath, true);

我的理解是,如果文件正在使用中或存在权限问题,则应抛出此错误,否则应删除目录及其所有内容。

但是,我偶尔会得到以下信息:

System.IO.IOException: The directory is not empty.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive)
    at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive)
    ...

有时会抛出该方法,我并不感到惊讶,但是当递归为true时,我会收到此特定消息感到惊讶。(我知道目录不为空。)

我会看到这个而不是AccessViolationException吗?


13
您不会看到AccessViolationException-这是针对无效的指针操作,而不是磁盘访问。
乔·怀特2009年

1
除了目录不为空(例如打开的文件句柄之类)之外,这似乎确实是某种IO问题。我会尝试使用递归删除选项,然后捕获IOException,搜索并关闭所有打开的文件句柄,然后重试。有一个关于讨论在这里: stackoverflow.com/questions/177146/...
丹Csharpster

Answers:


231

编者注:尽管此答案包含一些有用的信息,但实际上关于的工作是不正确的Directory.Delete。请阅读此答案的注释以及此问题的其他答案。


我以前遇到过这个问题。

问题的根源在于此功能不会删除目录结构中的文件。因此,您需要做的是创建一个函数,该函数先删除目录结构中的所有文件,然后删除所有目录,然后再删除目录本身。我知道这违反了第二个参数,但这是一种更安全的方法。此外,您可能希望在删除文件之前立即从文件中删除“只读”访问属性。否则,将引发异常。

只需将这段代码拍到您的项目中即可。

public static void DeleteDirectory(string target_dir)
{
    string[] files = Directory.GetFiles(target_dir);
    string[] dirs = Directory.GetDirectories(target_dir);

    foreach (string file in files)
    {
        File.SetAttributes(file, FileAttributes.Normal);
        File.Delete(file);
    }

    foreach (string dir in dirs)
    {
        DeleteDirectory(dir);
    }

    Directory.Delete(target_dir, false);
}

另外,对我来说,我个人对允许删除的计算机区域添加了限制,因为您希望有人在C:\WINDOWS (%WinDir%)或上调用此功能C:\


117
这是无稽之谈。Directory.Delete(myPath,true)是一个重载,它删除目录结构中的所有文件。如果您想弄错,请用Ryan S答案弄错。
Sig。Tolleranza 2010年

35
+1是因为尽管Directory.Delete()确实删除了其子目录中的文件(递归= true),但如果其中一个子目录或文件是只读的,它将抛出“ IOException:目录不为空”。所以这个解决方案的工作比Directory.Delete()
安东尼奥布赖恩

17
Directory.Delete(path, true)不删除文件的声明是错误的。请参阅MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Konstantin Spirin

20
-1是否可以有人明确指出这种方法的有效性值得怀疑。如果Directory.Delete(string,bool)失败,则某些事物将被锁定或错误使用,并且没有任何一种尺寸可以完全解决此问题。人们需要根据自己的情况来解决该问题,我们不应该大张旗鼓地将所有想法扔给这个问题(重试和吞下异常),并希望取得好的结果。
Ruben Bartelink

37
请注意这种方法,如果您要删除的目录具有指向其他文件夹的快捷方式/符号链接-您最终可能会删除比预期的多的文件
Chanakya 2012年

182

如果您尝试递归删除目录,a并且目录a\b已在资源管理器中打开,b将被删除,但a即使您在查找时它为空,也会收到“目录不为空”错误。任何应用程序(包括Explorer)的当前目录都保留该目录的句柄。呼叫时Directory.Delete(true),它会从下往上删除:b,然后a。如果b在Explorer中打开,则Explorer将检测到的删除b,向上更改目录cd ..并清理打开的句柄。由于文件系统异步运行,因此Directory.Delete由于与资源管理器冲突而导致操作失败。

解决方案不完整

我最初发布了以下解决方案,其想法是中断当前线程以使Explorer可以释放目录句柄。

// incomplete!
try
{
    Directory.Delete(path, true);
}
catch (IOException)
{
    Thread.Sleep(0);
    Directory.Delete(path, true);
}

但这仅在打开目录是要删除目录的直接子目录时才有效。如果a\b\c\d在Explorer中打开并使用了此功能a,则删除d和后该技术将失败c

更好的解决方案

即使在资源管理器中打开了一个较低级别的目录,此方法也将处理深度目录结构的删除。

/// <summary>
/// Depth-first recursive delete, with handling for descendant 
/// directories open in Windows Explorer.
/// </summary>
public static void DeleteDirectory(string path)
{
    foreach (string directory in Directory.GetDirectories(path))
    {
        DeleteDirectory(directory);
    }

    try
    {
        Directory.Delete(path, true);
    }
    catch (IOException) 
    {
        Directory.Delete(path, true);
    }
    catch (UnauthorizedAccessException)
    {
        Directory.Delete(path, true);
    }
}

尽管需要自己进行额外的递归工作,但我们仍然必须处理在执行过程UnauthorizedAccessException中可能发生的事情。尚不清楚第一次删除尝试是否为第二次成功尝试铺平了道路,还是仅仅是由于引发/捕获允许文件系统赶超的异常而引入的时间延迟。

通过Thread.Sleep(0)try块的开头添加a ,可以减少典型情况下引发和捕获的异常的数量。此外,在系统负载较重的情况下,您可能会经历两次Directory.Delete尝试而失败。将此解决方案视为更健壮的递归删除的起点。

一般答案

该解决方案仅解决了与Windows资源管理器进行交互的特殊性。如果要进行坚如磐石的删除操作,请牢记一件事:任何东西(病毒扫描程序等)都可以随时随地打开要删除的内容。因此,您必须稍后再试。多少时间后以及尝试多少次,取决于删除对象的重要性。如MSDN所示

健壮的文件迭代代码必须考虑文件系统的许多复杂性。

仅提供指向NTFS参考文档的链接的这份无辜的声明应该使您站起来。

编辑:很多。这个答案最初只有第一个不完整的解决方案。)


11
在Windows资源管理器中打开或选择路径或其中一个文件夹/文件时,确实会出现调用Directory.Delete(path,true)的情况。关闭Windows资源管理器并重新运行我现有的代码,而无需上面建议的try / catch可以正常工作。
大卫·阿尔珀特

1
我无法理解它的工作方式和原因,但是它对我有用,而设置文件属性和编写我自己的递归函数却没有。
斯蒂尔加2010年

1
@CarlosLiu因为它给“资源管理器提供了释放目录句柄的机会”
Dmitry Gonchar

4
发生的情况是系统要求资源管理器“释放目录句柄”,然后尝试删除目录。如果未及时删除目录句柄,则会引发异常并catch执行该块(与此同时,Explorer仍在释放目录,因为未发送任何命令告诉它不要这样做)。调用Thread.Sleep(0)或可能不需要调用,因为该catch块已经给了系统更多时间,但是它确实以低成本提供了一些额外的安全性。之后,Delete调用,目录已经释放。
Zachary Kniebel

1
@PandaWood实际上只有这个Sleep(100)对我有用。睡眠(0)无效。我不知道发生了什么以及如何正确解决此问题。我的意思是,如果这取决于服务器负载,将来应该是300或400,该怎么办?怎么知道 必须以另一种适当的方式……
Roman

43

在继续之前,请检查您所控制的以下原因:

  • 文件夹是否设置为进程的当前目录?如果是,请先将其更改为其他内容。
  • 您是否从该文件夹打开了文件(或加载了DLL)?(并且忘记关闭/卸载它)

否则,请检查以下超出您控制范围的合法原因:

  • 该文件夹中有标记为只读的文件。
  • 您没有删除其中某些文件的权限。
  • 该文件或子文件夹在资源管理器或其他应用程序中打开。

如果上述任何一个问题,在尝试改进删除代码之前,您应该了解为什么会发生这种情况。如果你的应用程序被删除只读或无法访问文件?谁这样标记他们的,为什么?

一旦排除了上述原因,仍然有可能发生虚假故障。如果有人持有要删除的任何文件或文件夹的句柄,则删除将失败,并且有许多原因可能导致某人枚举该文件夹或读取其文件:

  • 搜索索引器
  • 反病毒
  • 备份软件

处理虚假故障的一般方法是尝试多次,并在两次尝试之间暂停。您显然不想一直尝试下去,因此您应该在尝试了一定次数后放弃,并抛出异常或忽略错误。像这样:

private static void DeleteRecursivelyWithMagicDust(string destinationDir) {
    const int magicDust = 10;
    for (var gnomes = 1; gnomes <= magicDust; gnomes++) {
        try {
            Directory.Delete(destinationDir, true);
        } catch (DirectoryNotFoundException) {
            return;  // good!
        } catch (IOException) { // System.IO.IOException: The directory is not empty
            System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);

            // see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
            Thread.Sleep(50);
            continue;
        }
        return;
    }
    // depending on your use case, consider throwing an exception here
}

我认为,应该将所有此类删除操作都使用这样的助手,因为总是可能发生虚假失败。但是,您应该将此代码适应您的使用情况,而不仅仅是盲目地复制它。

我的应用程序生成的内部数据文件夹位于%LocalAppData%下,因此出现虚假故障,因此我的分析如下:

  1. 该文件夹完全由我的应用程序控制,并且用户没有正当理由将其标记为只读或在该文件夹中不可访问,因此我不会尝试处理这种情况。

  2. 那里没有用户创建的有价值的东西,因此不存在被错误强行删除的风险。

  3. 作为一个内部数据文件夹,我不希望它在资源管理器中打开,至少我不认为需要专门处理此案件(即,我可以通过支持很好地处理该案件)。

  4. 如果所有尝试均失败,则选择忽略该错误。最坏的情况是,该应用无法解压一些较新的资源,崩溃并提示用户联系支持,只要这种情况不经常发生,这对我来说是可以接受的。或者,如果应用程序没有崩溃,它将留下一些旧数据,这对我来说也是可以接受的。

  5. 我选择将重试限制为500ms(50 * 10)。这是一个在实践中起作用的任意阈值;我希望该阈值足够短,以至于用户认为应用程序已停止响应,因此它不会杀死该应用程序。另一方面,犯罪者有半秒钟的时间来完成我的文件夹的处理。从有时甚至Sleep(0)可以接受的其他SO答案来看,很少有用户会经历一次重试。

  6. 我每50毫秒重试一次,这是另一个任意数字。我认为,如果在尝试删除文件时正在处理文件(建立索引,检查文件),则50ms大约是正确的时间,可以期望在我的情况下完成处理。而且,50ms足够小,不会导致明显的减速。同样,Sleep(0)在许多情况下似乎足够了,所以我们不想延迟太多。

  7. 该代码将重试所有IO异常。我通常不希望访问%LocalAppData%的任何异常,因此我选择了简单性并接受了500ms延迟的风险,以防发生合法异常。我也不想找出一种方法来检测要重试的确切异常。


7
PPS几个月后,我很高兴地报告说,这段(有些疯狂)的代码已完全解决了该问题。有关此问题的支持请求降至零(从每周大约1-2个)。
Andrey Tarantsov

1
+0虽然这是一个更可靠的方法,但它的作用要小一些;与stackoverflow.com/a/7518831/11635相比,这是您的理想解决方案,对我而言,这同样适用-巧合编程-小心轻放。体现在代码中的一个有用的一点是,如果你要做一个重试,你需要考虑你是在比赛用的目录是否已自最后一次尝试[和niave“飘”模糊Directory.Exists后卫会尚未解决。]
鲁本·巴特林克

1
喜欢它...不知道我在做什么,这对我来说一直是一个痛点...但这不是因为我在资源管理器中打开了目录...互联网上对此并没有太大的争议-或更少的错误...至少我和Andrey有办法解决它:)
TCC

2
@RubenBartelink好的,所以我想我们可以就此达成共识:发布一段适用于特定应用程序的代码(从来就不适合每种情况),因为这样的答案对许多新手和新手都是不利的。 /或无知的开发人员。我将其作为定制的起点,但是,是的,有些人将按原样使用它,这是一件坏事。
Andrey Tarantsov 2014年

2
@nopara您不需要比较;如果我们不在循环中,那么我们就失败了。是的,在很多情况下,您将要引发异常,然后在堆栈中添加适当的错误处理代码,并可能带有用户可见的消息。
安德烈·塔兰佐夫

18

现代异步答案

公认的答案是完全错误的,它可能对某些人有用,因为从磁盘获取文件所花费的时间释放了锁定文件的时间。事实是,发生这种情况是因为文件被某些其他进程/流/动作锁定。其他答案使用Thread.Sleep(Yuck)在一段时间后重试删除目录。这个问题需要用更现代的答案重新审视。

public static async Task<bool> TryDeleteDirectory(
   string directoryPath,
   int maxRetries = 10,
   int millisecondsDelay = 30)
{
    if (directoryPath == null)
        throw new ArgumentNullException(directoryPath);
    if (maxRetries < 1)
        throw new ArgumentOutOfRangeException(nameof(maxRetries));
    if (millisecondsDelay < 1)
        throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));

    for (int i = 0; i < maxRetries; ++i)
    {
        try
        {
            if (Directory.Exists(directoryPath))
            {
                Directory.Delete(directoryPath, true);
            }

            return true;
        }
        catch (IOException)
        {
            await Task.Delay(millisecondsDelay);
        }
        catch (UnauthorizedAccessException)
        {
            await Task.Delay(millisecondsDelay);
        }
    }

    return false;
}

单元测试

这些测试显示了一个锁定文件如何导致 Directory.Delete失败以及上述TryDeleteDirectory方法如何解决该问题。

[Fact]
public async Task TryDeleteDirectory_FileLocked_DirectoryNotDeletedReturnsFalse()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            var result = await TryDeleteDirectory(directoryPath, 3, 30);
            Assert.False(result);
            Assert.True(Directory.Exists(directoryPath));
        }
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

[Fact]
public async Task TryDeleteDirectory_FileLockedThenReleased_DirectoryDeletedReturnsTrue()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        Task<bool> task;
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            task = TryDeleteDirectory(directoryPath, 3, 30);
            await Task.Delay(30);
            Assert.True(Directory.Exists(directoryPath));
        }

        var result = await task;
        Assert.True(result);
        Assert.False(Directory.Exists(directoryPath));
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

您能否扩展“现代”的含义?您的方法有什么好处?您认为其他人为什么错了?
TinyRacoon

1
其他人没有错。他们只是使用Thread.Sleep您今天应该避免使用的较旧的API ,而是使用async/ 代替。这是可以理解的,这是一个非常老的问题。awaitTask.Delay
穆罕默德·雷汉

由于BC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj,

@amonroejj您必须使用旧版本。那是固定的。
穆罕默德·雷汉

没有什么改善,而不是返回true if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); 来等待目录真正消失
fuchs777,

16

应该提到的一件事(我已将其添加为注释,但我不允许这样做)是,重载的行为已从.NET 3.5更改为.NET 4.0。

Directory.Delete(myPath, true);

从.NET 4.0开始,它将删除文件夹本身中的文件,但不会删除3.5中的文件。这也可以在MSDN文档中看到。

.NET 4.0

删除指定的目录,以及目录中的所有子目录和文件(如果有)。

.NET 3.5

删除一个空目录,并删除目录中的任何子目录和文件(如果有)。


3
我认为这只是文档更改...如果仅删除“空目录”,则意味着还删除带有2°参数的目录中的文件?如果为空,则没有文件...
Pisu

恐怕您以为是错的。在使用两个框架版本测试代码后,我已发布了此内容。在3.5中删除非空文件夹将引发异常。
jettatore

15

我在Delphi下遇到了同样的问题。最终结果是我自己的应用程序锁定了我要删除的目录。当我写入目录时,目录以某种方式被锁定(一些临时文件)。

catch 22是,在删除它之前,我对其父目录做了一个简单的更改目录


6
+1现在,Directory.Delete确实提到了msdn
Ruben Bartelink

3
有完整源代码示例的最终解决方案吗?
Kiquenet

11

令我惊讶的是,没有人想到这种简单的非递归方法,该方法可以删除包含只读文件的目录,而无需更改每个文件的只读属性。

Process.Start("cmd.exe", "/c " + @"rmdir /s/q C:\Test\TestDirectoryContainingReadOnlyFiles"); 

(将其更改为不立即触发cmd窗口,该窗口可在整个Internet上使用)


很高兴与我们分享,但是您是否愿意加入一些必要的更改以防止触发cmd窗口,而不是提示我们通过网络进行搜索?
ThunderGr 2012年

这行不通。在可以从命令提示符或资源管理器删除文件的相同情况下,使用此代码调用rmdir会得到退出代码145,该代码转换为“目录不为空”。它将目录留空,但仍然保留在目录中,就像Directory.Delete(“”,true)
Kevin Coulombe

@Kevin Coulombe,Humm ...您确定要使用/ s / q开关吗?
Piyush Soni

1
@KevinCoulombe:是的,必须是那些COM组件。当我尝试使用普通的旧C#时,它可以工作,并且确实删除目录以及其中的文件(只读或非只读文件)。
Piyush Soni

5
如果您开始依赖外部组件来处理框架中的内容,那么这是一个“不太理想”的想法,因为它不再具有可移植性(或者更加困难)。如果不存在该怎么办?还是/ option改变了?如果杰里米·爱德华兹(Jeremy Edwards)的解决方案行得通,那么应该首选恕我直言
frenchone

11

您可以通过运行以下命令来重现该错误:

Directory.CreateDirectory(@"C:\Temp\a\b\c\");
Process.Start(@"C:\Temp\a\b\c\");
Thread.Sleep(1000);
Directory.Delete(@"C:\Temp\a\b\c");
Directory.Delete(@"C:\Temp\a\b");
Directory.Delete(@"C:\Temp\a");

尝试删除目录'b'时,它将引发IOException“目录不为空”。这很愚蠢,因为我们刚刚删除了目录“ c”。

据我了解,解释是目录'c'被标记为已删除。但是删除尚未在系统中提交。系统已答复作业已完成,而实际上它仍在处理中。系统可能会等待文件浏览器将焦点放在父目录上以提交删除。

如果查看Delete函数的源代码(http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs),您会看到它使用了本机Win32Native.RemoveDirectory函数。此不等待行为在此处指出:

RemoveDirectory函数在关闭时将目录标记为要删除。因此,在关闭目录的最后一个句柄之前,不会删除目录。

http://msdn.microsoft.com/zh-cn/library/windows/desktop/aa365488(v=vs.85).aspx

睡眠和重试是解决方案。参见ryascl的解决方案。


8

我有一些奇怪的权限问题,尽管可以在资源管理器外壳中删除用户配置文件目录(在C:\ Documents和Settings中)。

File.SetAttributes(target_dir, FileAttributes.Normal);
Directory.Delete(target_dir, false);

对我来说,对目录执行“文件”操作是没有意义的,但是我知道它可以正常工作,对我来说就足够了!


2
当目录中有很多文件并且资源管理器正在打开包含这些文件的文件夹时,仍然没有希望。
看到

3

该答案基于:https : //stackoverflow.com/a/1703799/184528。我的代码的区别在于,我们仅在必要时递归调用许多delete子目录和文件。首次尝试删除失败(由于Windows资源管理器查看目录而导致的失败)。

    public static void DeleteDirectory(string dir, bool secondAttempt = false)
    {
        // If this is a second try, we are going to manually 
        // delete the files and sub-directories. 
        if (secondAttempt)
        {
            // Interrupt the current thread to allow Explorer time to release a directory handle
            Thread.Sleep(0);

            // Delete any files in the directory 
            foreach (var f in Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly))
                File.Delete(f);

            // Try manually recursing and deleting sub-directories 
            foreach (var d in Directory.GetDirectories(dir))
                DeleteDirectory(d);

            // Now we try to delete the current directory
            Directory.Delete(dir, false);
            return;
        }

        try
        {
            // First attempt: use the standard MSDN approach.
            // This will throw an exception a directory is open in explorer
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        }
        catch (UnauthorizedAccessException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        } 
    }

那么,如果有文件夹,应该如何删除该文件夹UnauthorizedAccessException?它会再次抛出。然后再次。再说一次,因为每次都要转到catch并再次调用该函数。A Thread.Sleep(0);不会更改您的权限。此时,它应该只记录错误并优雅地失败。只要打开(子)目录,此循环就会继续-不会以编程方式将其关闭。只要这些事情保持开放状态,我们是否准备让它这样做?有没有更好的办法?
vapcguy

如果有UnauthorizedAccessException,它将手动尝试手动删除每个文件。因此,它通过遍历目录结构继续取得进展。是的,可能每个文件和目录都将引发相同的异常,但这也可能仅由于资源管理器持有该异常而发生(请参见stackoverflow.com/a/1703799/184528),我会将“ tryAgain”更改为“ secondTry”使其更清楚。
cdiggins

为了更简洁地回答,它传递“ true”并执行不同的代码路径。
cdiggins

是的,看到了您的编辑,但我的意思不是删除文件,而是删除目录。我写了一些代码,基本上可以Process.Kill()在文件可能被锁定的任何进程上执行该操作,然后删除该文件。我遇到的问题是删除其中一个文件仍处于打开状态的目录时(请参阅stackoverflow.com/questions/41841590/…)。因此,回到此循环,无论它在做什么,如果Directory.Delete()再次在该文件夹上执行,如果无法释放该句柄,它将仍然失败。
vapcguy

而且UnauthorizedAccessException由于删除文件(假设甚至允许这样做,因为进入该代码后,它失败了Directory.Delete())也会发生同样的事情,这并没有神奇地赋予您删除目录的权限。
vapcguy

3

上述解决方案都不适合我。我最终使用了@ryascl解决方案的编辑版本,如下所示:

    /// <summary>
    /// Depth-first recursive delete, with handling for descendant 
    /// directories open in Windows Explorer.
    /// </summary>
    public static void DeleteDirectory(string path)
    {
        foreach (string directory in Directory.GetDirectories(path))
        {
            Thread.Sleep(1);
            DeleteDir(directory);
        }
        DeleteDir(path);
    }

    private static void DeleteDir(string dir)
    {
        try
        {
            Thread.Sleep(1);
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            DeleteDir(dir);
        }
        catch (UnauthorizedAccessException)
        {
            DeleteDir(dir);
        }
    }

2

您是否可能在争用条件下,另一个线程或进程正在将文件添加到目录中:

顺序为:

删除程序A:

  1. 清空目录
  2. 删除(现在为空)目录。

如果其他人在1和2之间添加文件,那么也许2会抛出列出的异常?


2

我花了几个小时来解决此问题以及删除目录的其他异常。这是我的解决方案

 public static void DeleteDirectory(string target_dir)
    {
        DeleteDirectoryFiles(target_dir);
        while (Directory.Exists(target_dir))
        {
            lock (_lock)
            {
                DeleteDirectoryDirs(target_dir);
            }
        }
    }

    private static void DeleteDirectoryDirs(string target_dir)
    {
        System.Threading.Thread.Sleep(100);

        if (Directory.Exists(target_dir))
        {

            string[] dirs = Directory.GetDirectories(target_dir);

            if (dirs.Length == 0)
                Directory.Delete(target_dir, false);
            else
                foreach (string dir in dirs)
                    DeleteDirectoryDirs(dir);
        }
    }

    private static void DeleteDirectoryFiles(string target_dir)
    {
        string[] files = Directory.GetFiles(target_dir);
        string[] dirs = Directory.GetDirectories(target_dir);

        foreach (string file in files)
        {
            File.SetAttributes(file, FileAttributes.Normal);
            File.Delete(file);
        }

        foreach (string dir in dirs)
        {
            DeleteDirectoryFiles(dir);
        }
    }

这段代码的延迟很小,这对我的应用而言并不重要。但是要小心,如果您要删除的目录中有很多子目录,则延迟可能对您来说是个问题。


8
-1什么是延迟?请不要巧合编程!
Ruben Bartelink '02年

1
@Ruben我不是说你错了。我只是说,仅为此投票是一项严厉的惩罚。我确实同意你的看法,但是,这4次投票没有产生4次投票。我也会赞成您的评论,但由于无法解释的延迟,我不会反对答案:)
ThunderGr 2012年

1
@RubenBartelink和其他人:虽然我不特别喜欢此代码(我已经发布了具有类似方法的另一种解决方案),但这里的延迟是合理的。该问题很可能超出了应用程序的控制范围;也许另一个应用程序会定期重新扫描FS,从而在短时间内锁定文件夹。延迟解决了这个问题,使错误报告计数降至零。谁在乎我们是否根本没有根源的想法?
Andrey Tarantsov

1
@RubenBartelink实际上,当您考虑它时,在此删除NTFS目录期间使用延迟重试方法是不负责任的解决方案。任何正在进行的文件遍历都会阻止删除,因此它迟早会失败。而且,您不能指望所有第三方搜索,备份,防病毒和文件管理工具都不会出现在您的文件夹中。
Andrey Tarantsov

1
@RubenBartelink另一个例子,例如,您给出100ms的延迟,而目标PC上任何软件的最长锁定时间是AV软件= 90ms。假设它还具有可将文件锁定70毫秒的备份软件。现在,AV锁定了一个文件,您的应用程序等待100毫秒,这通常可以,但是随后遇到另一个锁定,因为备份软件在AV扫描的70毫秒标记处开始抓取文件,因此还需要40毫秒来释放文件。因此,虽然AV软件花费的时间更长,并且您的100ms通常比这两个应用程序中的任何一个都更长,但是您仍然必须考虑它在中间启动的时间。
vapcguy

2

不会删除文件的递归目录删除当然是意外的。我的解决办法:

public class IOUtils
{
    public static void DeleteDirectory(string directory)
    {
        Directory.GetFiles(directory, "*", SearchOption.AllDirectories).ForEach(File.Delete);
        Directory.Delete(directory, true);
    }
}

我遇到过这样的情况,但有帮助,但通常,Directory.Delete在递归删除时删除目录中的文件,如msdn中所述

作为Windows资源管理器的用户,我有时也会遇到这种不规则行为:有时我无法删除文件夹(它认为无意义的消息是“拒绝访问”),但是当我向下钻取并删除下部项目时,我可以删除上部项目。因此,我想上面的代码处理的是操作系统异常,而不是基类库问题。


1

目录或其中的文件已锁定,无法删除。查找锁定它的罪魁祸首,看看是否可以消除它。


T1000打开用户文件夹:“您被终止了!”
vapcguy

1

似乎在Windows资源管理器中选择了路径或子文件夹足以阻止Directory.Delete(path,true)的单次执行,如上所述抛出IOException并死掉,而不是将Windows资源管理器引导到父文件夹并按以下步骤进行操作预期。


这似乎是我的问题。一旦我关闭资源管理器并再次运行,也不例外。甚至选择父母的父母还不够。我实际上必须关闭资源管理器。
Scott Marlowe

是的,这是有原因的。因此,有任何想法如何以编程方式处理它,还是答案始终是确保所有1000个用户都关闭了该文件夹?
vapcguy

1

我今天有这个问题。之所以发生这种情况,是因为我将Windows资源管理器打开到试图删除的目录,导致递归调用失败,从而导致IOException。确保没有打开目录的句柄。

同样,MSDN明确表明您不必编写自己的记录:http : //msdn.microsoft.com/zh-cn/library/fxeahc5f.aspx


1

我在使用TFS2012的构建服务器上的Windows Workflow Foundation遇到了相同的问题。在内部,将递归标志设置为true的工作流称为Directory.Delete()。在我们的案例中,这似乎与网络有关。

在重新创建并使用最新的二进制文件填充之前,我们正在删除网络共享上的二进制文件放置文件夹。其他所有构建都会失败。在失败的构建后打开放置文件夹时,该文件夹为空,这表示Directory.Delete()调用的每个方面都成功,但删除实际目录除外。

该问题似乎是由网络文件通信的异步性质引起的。生成服务器告诉文件服务器删除所有文件,并且文件服务器报告已删除所有文件,即使尚未完全完成。然后,构建服务器请求删除目录,而文件服务器拒绝该请求,因为它尚未完全完成文件的删除。

我们的情况有两种可能的解决方案:

  • 在我们自己的代码中建立递归删除,并在每个步骤之间进行延迟和验证
  • IOException之后重试X次,从而在重试之前产生延迟

后一种方法既快速又肮脏,但似乎可以解决问题。


1

这是由于FileChangesNotifications。

从ASP.NET 2.0开始发生。当您删除应用程序中的某些文件夹时,它将重新启动。您可以使用ASP.NET Health Monitoring自己查看它 。

只需将以下代码添加到您的web.config / configuration / system.web中:

<healthMonitoring enabled="true">
  <rules>
    <add name="MyAppLogEvents" eventName="Application Lifetime Events" provider="EventLogProvider" profile="Critical"/>
  </rules>
</healthMonitoring>


在那之后退房Windows Log -> Application。到底是怎么回事:

删除文件夹时,如果有子文件夹,Delete(path, true)请先删除子文件夹。FileChangesMonitor知道有关删除并关闭您的应用程序就足够了。同时,您的主目录尚未删除。这是来自Log的事件:


在此处输入图片说明


Delete() 尚未完成工作,并且由于应用正在关闭,因此引发了异常:

在此处输入图片说明

当您要删除的文件没有任何子文件夹时,Delete()只会删除所有文件和该文件夹,应用程序也将重新启动,但是您不会出现任何异常,因为应用程序重新启动不会中断任何操作。但是,您仍然会丢失所有进程中的会话,重新启动时应用无法响应请求等。

现在怎么办?

有一些变通和调整,以禁用此行为,目录连接关闭FCN与注册使用FileChangesMonitor反射(由于没有裸露的方法)停止,但他们似乎都不会是正确的,因为FCN是有一个原因。它负责管理应用程序的结构,而不是数据的结构。简短的答案是:将要删除的文件夹放在应用程序外部。FileChangesMonitor将不会收到任何通知,并且您的应用不会每次都重新启动。您不会有例外。要使它们在网络上可见,有两种方法:

  1. 使一个控制器处理传入呼叫,然后通过从应用程序外部文件夹(wwwroot外部)中读取来提供文件。

  2. 如果您的项目很大并且性能最重要,请设置单独的小型且快速的Web服务器来提供静态内容。因此,您将把自己的工作留给IIS。它可能在同一台计算机(对于Windows是mongoose)或另一台计算机(对于Linux是nginx)上。好消息是您无需额外的Microsoft许可即可在Linux上设置静态内容服务器。

希望这可以帮助。


1

如上所述,“接受的”解决方案在重新解析点上失败-但是人们仍然将其标记为(???)。有一个更短的解决方案可以正确地复制功能:

public static void rmdir(string target, bool recursive)
{
    string tfilename = Path.GetDirectoryName(target) +
        (target.Contains(Path.DirectorySeparatorChar.ToString()) ? Path.DirectorySeparatorChar.ToString() : string.Empty) +
        Path.GetRandomFileName();
    Directory.Move(target, tfilename);
    Directory.Delete(tfilename, recursive);
}

我知道,不会处理稍后提到的权限情况,但是出于所有目的和目的,FAR BETTER提供了原始Directory / stock Directory.Delete()的预期功能-而且代码也少得多

您可以放心地进行处理,因为旧目录将不受影响...即使没有删除, 也因为“文件系统仍在追赶”(或MS为提供功能中断提供的任何借口)

这样做的好处是,如果您知道目标目录很大/很深并且不想等待(或打扰例外),则最后一行可以替换为:

    ThreadPool.QueueUserWorkItem((o) => { Directory.Delete(tfilename, recursive); });

您仍然可以安全地进行工作。


2
可以通过以下方式简化您的分配:字符串tfilename = Path.Combine(Path.GetDirectoryName(target),Path.GetRandomFileName());
皮特

1
我必须同意皮特的观点。编写的代码不会添加分隔符。它走了我的路,\\server\C$\dir并成功了\\server\C$asf.yuw。结果,我遇到了一个错误Directory.Move()- Source and destination path must have identical roots. Move will not work across volumes. 一旦使用Pete的代码,EXCEPT既不能处理锁定文件或打开目录的问题,也可以正常工作-因此它永远不会进入ThreadPool命令。
vapcguy

注意:此答案仅应与recursive = true一起使用。如果为false,则即使目录不为空,也将移动目录。这将是一个错误;在这种情况下,正确的行为是引发异常,并保持目录不变。
ToolmakerSteve

1

当目录(或任何子目录)中的文件的路径长度大于260个符号时,在Windows上可能会出现此问题。

在这种情况下,您需要删除\\\\?\C:\mydir而不是C:\mydir。您可以在此处阅读有关260个符号的限制。


1

我已经解决了这一千禧年技术(您可以将Thread.Sleep留在自己手上)

bool deleted = false;
        do
        {
            try
            {
                Directory.Delete(rutaFinal, true);                    
                deleted = true;
            }
            catch (Exception e)
            {
                string mensaje = e.Message;
                if( mensaje == "The directory is not empty.")
                Thread.Sleep(50);
            }
        } while (deleted == false);

0

如果您的应用程序(或任何其他应用程序)的当前目录是您要删除的目录,则它不是访问冲突错误,但目录不为空。通过更改当前目录,确保它不是您自己的应用程序;另外,请确保未在其他程序(例如Word,excel,Total Commander等)中打开目录。大多数程序将cd到最后打开的文件的目录,这将导致该情况。


0

对于网络文件,Directory.DeleteHelper(recursive:= true)可能会导致IOException,这是由于删除文件的延迟引起的



0

如果任何文件或目录都在使用中,则会发生此错误。这是一个误导性错误。检查是否有打开到树中任何目录的浏览器窗口或命令行窗口,或正在使用该树中文件的程序。


0

当方法异步并按如下所示编码时,我解决了所述问题的一个可能实例:

// delete any existing update content folder for this update
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
       await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

有了这个:

bool exists = false;                
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
    exists = true;

// delete any existing update content folder for this update
if (exists)
    await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

结论?摆脱了用于检查Microsoft无法讲话的存在性的句柄有一些异步方面。就像if语句中的异步方法具有if语句的行为就像using语句一样。


0

您不必创建递归的额外方法或删除文件夹Extra中的文件。这一切都通过调用自动完成

DirectoryInfo.Delete();

详细信息在这里

像这样的东西效果很好:

  var directoryInfo = new DirectoryInfo("My directory path");
    // Delete all files from app data directory.

    foreach (var subDirectory in directoryInfo.GetDirectories())
    {
          subDirectory.Delete(true);// true set recursive paramter, when it is true delete sub file and sub folder with files too
    }

将true作为变量传递给delete方法,也会删除子文件和带有文件的子文件夹。


-2

上面的答案对我都不起作用。看来我自己的应用对DirectoryInfo在目标目录上导致其保持锁定状态。

强制垃圾回收似乎可以解决此问题,但不能立即解决。几次尝试在需要的地方删除。

请注意,Directory.Exists因为异常后它可能会消失。我不知道为什么我的删除操作被延迟(Windows 7 SP1)

        for (int attempts = 0; attempts < 10; attempts++)
        {
            try
            {
                if (Directory.Exists(folder))
                {
                    Directory.Delete(folder, true);
                }
                return;
            }
            catch (IOException e)
            {
                GC.Collect();
                Thread.Sleep(1000);
            }
        }

        throw new Exception("Failed to remove folder.");

1
-1巧合编程。GC会做什么对象?这在任何方面都是好的一般建议吗?(我相信您,当您说自己有问题并且使用了这段代码,并且觉得自己现在没有问题,但这不是重点)
Ruben Bartelink 2014年

@RubenBartelink我同意。这是一个hack。尚不清楚解决方案或解决方案的Voodoo代码。我希望有一个适当的解决方案。
Reactgular 2014年

1
我的问题是,它在stackoverflow.com/a/14933880/11635之上添加的任何内容都是高度投机的。如果可以的话,我会给出-1表示重复,而-1表示推测/编程是巧合。洒水GC.Collect是:a)不好的建议; b)锁死的dirs并不是值得推荐的普遍原因。只是选择其中一个,不要在无辜读者的脑海中
散播

3
使用GC.WaitForPendingFinalizers(); 在GC.Collect()之后;这将按预期工作。
Heiner 2014年

不确定,未经测试,但也许最好做一个using声明,然后: using (DirectoryInfo di = new DirectoryInfo(@"c:\MyDir")) { for (int attempts = 0; attempts < 10; attempts++) { try { if (di.Exists(folder)) { Directory.Delete(folder, true); } return; } catch (IOException e) { Thread.Sleep(1000); } } }
vapcguy

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.