7

I have a folder in Windows Server with subfolders and ≈50000 files. When I click the right mouse button and choose delete (or shift+delete) – all files are deleted in 10-20 seconds.

When I delete files using code – 1500-4000 seconds.

Delete large number of files – don't work for me.

My code:

string folderPath = @"C://myFolder";
DirectoryInfo folderInfo = new DirectoryInfo(folderPath);
folderInfo.Delete(true); // true - recursive, with sub-folders

How to delete files faster?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291

3 Answers3

9

A much faster way to delete files is to use the Windows functions instead of the .NET ones.

You will need to first import the function:

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteFile(string lpFileName);

And then you can do this:

string[] files = Directory.EnumerateFiles(path, "*". SearchOption.AllDirectories);

foreach (string file in files)
{
    DeleteFile(file);
}

Once the files are deleted, which is the slowest part by using the managed APIs, you can call Directory.DeleteFolder(path, true) to delete the empty folders.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • This is not recursive folder delete – gabba Jun 22 '17 at 12:23
  • @gabba I didn't say it was, I was showing a faster way. Just wrap it in a foreach, it's not that hard... – Camilo Terevinto Jun 22 '17 at 12:23
  • The question is about... And with recursive scan of folders it would not be so fast – gabba Jun 22 '17 at 12:25
  • @gabba I updated my code. No need for recursion, just get all paths first and delete them – Camilo Terevinto Jun 22 '17 at 12:26
  • 1
    @CamiloTerevinto This will delete only **FILES** and not **DIRECTORIES** about which is that question. – mrogal.ski Jun 22 '17 at 12:27
  • 1
    @m.rogalski after calling this, calling Directory.DeleteFolder to delete the empty folders is much, much faster – Camilo Terevinto Jun 22 '17 at 12:29
  • @CamiloTerevinto Its not removes sub folders. Directory.GetFiles do recursion inside and it cant be fast so fast because it wrap – gabba Jun 22 '17 at 12:29
  • @gabba I used this solution to delete the `%temp%` folder in an application and was much faster than with `Directory.DeleteFolder`. And the application normally had to delete well over 20GB in over 20 thousand files – Camilo Terevinto Jun 22 '17 at 12:32
  • RD /Q /S _folder_ probably should be checked also – Steve Jun 22 '17 at 12:36
  • This method slow to. Without foreach I use: files.AsParallel().ForAll(file => DeleteFile(file)); 10 minutes, for deleting 4000 files. – Alexander Brattsev Jun 22 '17 at 12:39
  • @AlexanderBrattsev you will need to edit your question or post a dotnetfiddle. This is not slow by any means. – Camilo Terevinto Jun 22 '17 at 12:40
  • @CamiloTerevinto Regarding speed: was your 20GB >20000 files example with many sub-folders? Because getting 20000 files in a single folder would be much faster than a considerable amount of sub-recursions... – grek40 Jun 22 '17 at 12:42
  • 2
    @AlexanderBrattsev Don't think it is a good idea to do parallel IO operations on the same single disk. – Steve Jun 22 '17 at 12:43
  • @grek40 it was the temp folder, there was any amount of folders with sub folders. It was basically a cleanup tool for users that had had their laptop for over 4-5 years. The tool once cleaned over 60GB in less than 5 minutes (and the temp folder was only one of the folders it cleaned) – Camilo Terevinto Jun 22 '17 at 12:47
  • 1
    @AlexanderBrattsev AsParallel() does not what you hope it would in this case. It would at best result in the same performance, probably worse. – Mixxiphoid Jun 22 '17 at 12:48
  • Is there a rationale behind using `GetFiles` instead of `EnumerateFiles`? – grek40 Jun 22 '17 at 13:01
  • @grek40 yes, that I meant EnumerateFiles rather than GetFiles but my memory didn't work well :) – Camilo Terevinto Jun 22 '17 at 13:01
  • @CamiloTerevinto - thanks for answer. Still slow. Now I use your code, and multithreading. I delete folder with ~30 servers, and consistently cause deletion of files - will not fit. Files are added faster than removed! p.s. thank for the advice 'EnumerateFiles' =) – Alexander Brattsev Jun 22 '17 at 14:22
  • 1
    @AlexanderBrattsev wait a second... did you just say that you create new files while the old ones are removed? You know this will split the disk activity between enumerating, removing and adding? How can you have any reliable performance measurement with such a setup? – grek40 Jun 22 '17 at 21:31
  • @grek40 Yes, I understand how it works HDD. My question was - how to delete a folder with files in C# as fast as it is removed Windows. Windows removes in 10 seconds, why is my program deletes per hour? Windows just forgets about the files? Marks them as deleted? I want my program worked the same way. – Alexander Brattsev Jun 23 '17 at 08:00
  • @AlexanderBrattsev Since you mention 30 servers... do you run a local delete program on each of the servers or do you have one central programm that is managing the delete on each of the servers via network share? – grek40 Jun 23 '17 at 08:03
  • @grek40 One Central program. Yesterday I upgraded deleting files, using the library kernel32.dll and multi-threading. Servers cleared in 10 hours, it suited me. It remains only of academic interest to me. Can I use WinApi for the network to quickly delete files? If not, the correct answer was given. P. S. sorry for my bad English – Alexander Brattsev Jun 23 '17 at 08:17
  • @AlexanderBrattsev Point is, the network based access makes a huge difference to the question (really huge!!) and you get local-access focused questions because you asked about a `C:something` path. – grek40 Jun 23 '17 at 08:20
  • @grek40 thanks for the help. I will think how to create another question =) – Alexander Brattsev Jun 23 '17 at 08:34
2

Since the question is actually about deleting network shared folders and it's stated that the explorer based delete is much faster than the C# internal delete mechanism, it might help to just invoke a windows shell based delete.

ProcessStartInfo Info = new ProcessStartInfo(); 
Info.Arguments = "/C rd /s /q \"<your-path>\""; 
Info.WindowStyle = ProcessWindowStyle.Hidden; 
Info.CreateNoWindow = true; 
Info.FileName = "cmd.exe"; 
Process.Start(Info);

Ofcourse, you have to replace <your-path>.

However, I don't have the infrastructure and files available to test the performance myself right now.

grek40
  • 13,113
  • 1
  • 24
  • 50
1

Not quite sure why the method DirectoryInfo.Delete() takes too much time when deleting folders that have a lot of files and sub-folders. I suspect that the method may also do quite a few things that are unnecessary.

I write a small class to to use Win API without doing too many unnecessary things to test my idea. It takes about 40 seconds to delete a folder that have 50,000 files and sub-folders. So, hope it helps.

I use this PowerScript to generate the testing files.

$folder = "d:\test1";
For ($i=0; $i -lt 50000; $i++)
{
    New-Item -Path $folder -Name "test$i.txt" -ItemType "file" -Value $i.ToString();
}

The following is the code in C#.

using System;
using System.Collections.Generic;
//
using System.Runtime.InteropServices;
using System.IO;
//

namespace TestFileDelete
{
    class FileDelete
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct WIN32_FIND_DATAW
        {
            public FileAttributes dwFileAttributes;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            public UInt32 nFileSizeHigh;  //  DWORD
            public UInt32 nFileSizeLow;  //  DWORD
            public UInt32 dwReserved0;    //  DWORD
            public UInt32 dwReserved1;  //  DWORD
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public String cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            public String cAlternateFileName;
        };


        static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern IntPtr FindFirstFileW(String lpFileName, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern Boolean FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll")]
        private static extern Boolean FindClose(IntPtr handle);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern Boolean DeleteFileW(String lpFileName);    //  Deletes an existing file

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern Boolean RemoveDirectoryW(String lpPathName);   //  Deletes an existing empty directory


        //  This method check to see if the given folder is empty or not.
        public static Boolean IsEmptyFolder(String folder)
        {
            Boolean res = true;

            if (folder == null && folder.Length == 0)
            {
                throw new Exception(folder + "is invalid");
            }

            WIN32_FIND_DATAW findFileData;
            String searchFiles = folder + @"\*.*";
            IntPtr searchHandle = FindFirstFileW(searchFiles, out findFileData);
            if (searchHandle == INVALID_HANDLE_VALUE)
            {
                throw new Exception("Cannot check folder " + folder);
            }

            do
            {
                if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                {
                    //  found a sub folder
                    if (findFileData.cFileName != "." && findFileData.cFileName != "..")
                    {
                        res = false;
                        break;
                    }

                }   //  if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                else
                {
                    //  found a file
                    res = false;
                    break;
                }
            } while (FindNextFileW(searchHandle, out findFileData));

            FindClose(searchHandle);
            return res;
        }   //  public static Boolean IsEmptyFolder(String folder)

        //  This method deletes the given folder
        public static Boolean DeleteFolder(String folder)
        {
            Boolean res = true;
            //  keep non-empty folders to delete later (after we delete everything inside)
            Stack<String> nonEmptyFolder = new Stack<String>();
            String currentFolder = folder;
            do
            {
                Boolean isEmpty = false;
                try
                {
                    isEmpty = IsEmptyFolder(currentFolder);
                }
                catch (Exception ex)
                {
                    //  Something wrong
                    res = false;
                    break;
                }

                if (!isEmpty)
                {
                    nonEmptyFolder.Push(currentFolder);
                    WIN32_FIND_DATAW findFileData;
                    IntPtr searchHandle = FindFirstFileW(currentFolder + @"\*.*", out findFileData);
                    if (searchHandle != INVALID_HANDLE_VALUE)
                    {
                        do
                        {   //  for each folder, find all of its sub folders and files
                            String foundPath = currentFolder + @"\" + findFileData.cFileName;
                            if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                            {
                                //  found a sub folder
                                if (findFileData.cFileName != "." && findFileData.cFileName != "..")
                                {
                                    if (IsEmptyFolder(foundPath))
                                    {   //  found an empty folder, delete it
                                        if (!(res = RemoveDirectoryW(foundPath)))
                                        {
                                            Int32 error = Marshal.GetLastWin32Error();
                                            break;
                                        }
                                    }
                                    else
                                    {   //  found a non-empty folder
                                        nonEmptyFolder.Push(foundPath);
                                    }
                                }   //  if (findFileData.cFileName != "." && findFileData.cFileName != "..")

                            }   //  if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                            else
                            {
                                //  found a file, delete it
                                if (!(res = DeleteFileW(foundPath)))
                                {
                                    Int32 error = Marshal.GetLastWin32Error();
                                    break;
                                }
                            }

                        } while (FindNextFileW(searchHandle, out findFileData));

                        FindClose(searchHandle);

                    }   //  if (searchHandle != INVALID_HANDLE_VALUE)

                }//  if (!IsEmptyFolder(folder))
                else
                {
                    if (!(res = RemoveDirectoryW(currentFolder)))
                    {
                        Int32 error = Marshal.GetLastWin32Error();
                        break;
                    }
                }

                if (nonEmptyFolder.Count > 0)
                {
                    currentFolder = nonEmptyFolder.Pop();
                }
                else
                {
                    currentFolder = null;
                }
            } while (currentFolder != null && res);

            return res;
        }   //  public static Boolean DeleteFolder(String folder)
    };

    class Program
    {
        static void Main(string[] args)
        {
            DateTime t1 = DateTime.Now;

            try
            {
                Boolean b = FileDelete.DeleteFolder(@"d:\test1");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            DateTime t2 = DateTime.Now;
            TimeSpan ts = t2 - t1;
            Console.WriteLine(ts.Seconds);

        } 
    } 
}
Tuan Le PN
  • 364
  • 1
  • 12