Cannot delete directory with Directory.Delete(path

2019-01-01 03:17发布

I'm using .NET 3.5, trying to recursively delete a directory using:

Directory.Delete(myPath, true);

My understanding is that this should throw if files are in use or there is a permissions problem, but otherwise it should delete the directory and all of its contents.

However, I occasionally get this:

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)
    ...

I'm not surprised that the method sometimes throws, but I'm surprised to get this particular message when recursive is true. (I know the directory is not empty.)

Is there a reason I'd see this instead of AccessViolationException?

29条回答
不再属于我。
2楼-- · 2019-01-01 03:19

I'm surprised that no one thought of this simple non-recursive method, which can delete directories containing read only files, without needing to change read only attribute of each of them.

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

(Change a bit to not to fire a cmd window momentarily, which is available all over the internet)

查看更多
笑指拈花
3楼-- · 2019-01-01 03:20

in case of network files, Directory.DeleteHelper(recursive:=true) might cause IOException which caused by the delay of deleting file

查看更多
旧时光的记忆
4楼-- · 2019-01-01 03:22

Is it possible you have a race condition where another thread or process is adding files to the directory:

The sequence would be:

Deleter process A:

  1. Empty the directory
  2. Delete the (now empty) directory.

If someone else adds a file between 1 & 2, then maybe 2 would throw the exception listed?

查看更多
君临天下
5楼-- · 2019-01-01 03:26

Before going further, check for the following reasons that are under your control:

  • Is the folder set as a current directory of your process? If yes, change it to something else first.
  • Have you opened a file (or loaded a DLL) from that folder? (and forgot to close/unload it)

Otherwise, check for the following legitimate reasons outside of your control:

  • There are files marked as read-only in that folder.
  • You don't have a deletion permission to some of those files.
  • The file or subfolder is open in Explorer or another app.

If any of the above is the problem, you should understand why it happens before trying to improve your deletion code. Should your app be deleting read-only or inaccessible files? Who marked them that way, and why?

Once you have ruled out the above reasons, there's still a possibility of spurious failures. The deletion will fail if anyone holds a handle to any of the files or folders being deleted, and there are many reasons why someone may be enumerating the folder or reading its files:

  • search indexers
  • anti-viruses
  • backup software

The general approach to deal with spurious failures is to try multiple times, pausing between the attempts. You obviously don't want to keep trying forever, so you should give up after a certain number of attempts and either throw an exception or ignore the error. Like this:

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
}

In my opinion, a helper like that should be used for all deletions because spurious failures are always possible. However, YOU SHOULD ADAPT THIS CODE TO YOUR USE CASE, not just blindly copy it.

I had spurious failures for an internal data folder generated by my app, located under %LocalAppData%, so my analysis goes like this:

  1. The folder is controlled solely by my application, and the user has no valid reason to go and mark things as read-only or inaccessible inside that folder, so I don't try to handle that case.

  2. There's no valuable user-created stuff in there, so there's no risk of forcefully deleting something by mistake.

  3. Being an internal data folder, I don't expect it to be open in explorer, at least I don't feel the need to specifically handle the case (i.e. I'm fine handling that case via support).

  4. If all attempts fail, I choose to ignore the error. Worst case, the app fails to unpack some newer resources, crashes and prompts the user to contact support, which is acceptable to me as long as it does not happen often. Or, if the app does not crash, it will leave some old data behind, which again is acceptable to me.

  5. I choose to limit retries to 500ms (50 * 10). This is an arbitrary threshold which works in practice; I wanted the threshold to be short enough so that users wouldn't kill the app, thinking that it has stopped responding. On the other hand, half a second is plenty of time for the offender to finish processing my folder. Judging from other SO answers which sometimes find even Sleep(0) to be acceptable, very few users will ever experience more than a single retry.

  6. I retry every 50ms, which is another arbitrary number. I feel that if a file is being processed (indexed, checked) when I try to delete it, 50ms is about the right time to expect the processing to be completed in my case. Also, 50ms is small enough to not result in a noticeable slowdown; again, Sleep(0) seems to be enough in many cases, so we don't want to delay too much.

  7. The code retries on any IO exceptions. I don't normally expect any exceptions accessing %LocalAppData%, so I chose simplicity and accepted the risk of a 500ms delay in case a legitimate exception happens. I also didn't want to figure out a way to detect the exact exception that I want to retry on.

查看更多
浮光初槿花落
6楼-- · 2019-01-01 03:26

I've had this same problem with Windows Workflow Foundation on a build server with TFS2012. Internally, the workflow called Directory.Delete() with the recursive flag set to true. It appears to be network related in our case.

We were deleting a binary drop folder on a network share before re-creating and re-populating it with the latest binaries. Every other build would fail. When opening the drop folder after a failed build, the folder was empty, which indicates that every aspect of the Directory.Delete() call was successful except for deleting the actually directory.

The problem appears to be caused by the asynchronous nature of network file communications. The build server told the file server to delete all of the files and the file server reported that it had, even though it wasn't completely finished. Then the build server requested that the directory be deleted and the file server rejected the request because it hadn't completely finished deleting the files.

Two possible solutions in our case:

  • Build up the recursive deletion in our own code with delays and verifications between each step
  • Retry up to X times after an IOException, giving a delay before trying again

The latter method is quick and dirty but seems to do the trick.

查看更多
后来的你喜欢了谁
7楼-- · 2019-01-01 03:26

I resolved one possible instance of the stated problem when methods were async and coded like this:

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

With this:

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);

Conclusion? There is some asynchronous aspect of getting rid of the handle used to check existence that Microsoft has not been able to speak to. It's as if the asynchronous method inside an if statement has the if statement acting like a using statement.

查看更多
登录 后发表回答