使用c#删除大量(>;100K)文件,同时保持web应用程序的性能?

使用c#删除大量(>;100K)文件,同时保持web应用程序的性能?,c#,file-io,file-management,C#,File Io,File Management,我试图从一个位置删除大量的文件(我指的是超过100000个),从而从一个网页启动操作。显然我可以用 string[] files = System.IO.Directory.GetFiles("path with files to delete"); foreach (var file in files) { IO.File.Delete(file); } Directory.GetFiles 此方法已发布过几次: 和 但是,这种方法的问题是,如果您有十万个文件,那么它将成为一个性

我试图从一个位置删除大量的文件(我指的是超过100000个),从而从一个网页启动操作。显然我可以用

string[] files = System.IO.Directory.GetFiles("path with files to delete");
foreach (var file in files) {
    IO.File.Delete(file);
}
Directory.GetFiles

此方法已发布过几次: 和

但是,这种方法的问题是,如果您有十万个文件,那么它将成为一个性能问题,因为它必须先生成所有文件路径,然后再循环通过它们

此外,如果一个网页正在等待一个方法的响应,而该方法正在执行此操作,您可以想象它看起来有点垃圾

我的一个想法是将其封装在一个非同步的web服务调用中,当调用完成时,它会向web页面发出响应,说它们已被删除?也许把delete方法放在一个单独的线程中?或者甚至可以使用单独的批处理来执行删除

当我试图计算目录中的文件数时,如果它包含大量文件,我也会遇到类似的问题


我想知道这是不是有点过分了?即,是否有更简单的方法来处理此问题?任何帮助都将不胜感激。

在单独的线程中执行,或将消息发布到队列(可能?),其中另一个应用程序(可能是windows服务)订阅该队列,并在其自己的进程中执行命令(即“Delete e:\dir*.txt”)


邮件可能只包含文件夹名称。如果您使用类似于和事务性队列的东西,那么只要消息发布成功,您就可以发布消息并立即返回。如果实际处理消息时出现问题,那么它将重试,并最终进入一个您可以查看和执行维护的进程。

将工作线程引导到工作线程,然后将响应返回给用户

我会标记一个应用程序变量,表示您正在执行“大删除作业”,以停止运行多个执行相同工作的线程。然后,您可以轮询另一个页面,该页面可以为您提供迄今为止删除的文件数的进度更新,如果您愿意的话

只是一个问题,为什么会有这么多文件

  • GetFiles
    非常慢
  • 如果你是从一个网站上调用它,你可能会抛出一个新的线程来实现这个技巧
  • 返回是否仍有匹配文件的ASP.NET AJAX调用可用于执行基本进度更新
  • 下面是一个针对
    GetFiles
    的快速Win32包装的实现,将其与新线程和AJAX函数结合使用,如:
    getfileunmanaged(@“C:\myDir”,“*.txt*)。GetEnumerator().MoveNext()

    用法

    Thread workerThread = new Thread(new ThreadStart((MethodInvoker)(()=>
    {    
         foreach(var file in GetFilesUnmanaged(@"C:\myDir", "*.txt"))
              File.Delete(file);
    })));
    workerThread.Start();
    //just go on with your normal requests, the directory will be cleaned while the user can just surf around
    

    公共静态IEnumerable GetFileUnmanaged(字符串目录,字符串筛选器)
    {
    返回新的FileFinder(Path.Combine(目录、筛选器))
    其中(f=>(f.Attributes&FileAttributes.Normal)=FileAttributes.Normal
    ||(f.Attributes&FileAttributes.Archive)==FileAttributes.Archive)
    .选择(s=>s.FileName);
    }
    }
    公共类文件枚举器:IEnumerator
    {
    #区域互操作导入
    private const int ERROR_FILE_NOT_FOUND=2;
    private const int ERROR\u NO\u MORE\u FILES=18;
    [DllImport(“kernel32.dll”,SetLastError=true,CharSet=CharSet.Auto)]
    私有静态外部IntPtr FindFirstFile(字符串lpFileName,out WIN32_FIND_DATA lpFindFileData);
    [DllImport(“kernel32.dll”,SetLastError=true,CharSet=CharSet.Auto)]
    私有静态外部bool FindNextFile(安全句柄hFindFile,out WIN32_FIND_DATA lpFindFileData);
    #端区
    #区域数据成员
    私有只读字符串\u文件名;
    私人保险箱手柄(findHandle);;
    私有WIN32_FIND_DATA_win32FindData;
    #端区
    公共文件枚举器(字符串文件名)
    {
    _fileName=文件名;
    _findHandle=null;
    _win32FindData=新的WIN32_FIND_DATA();
    }
    #区域IEnumerator成员
    公共FoundFileData当前
    {
    得到
    {
    if(_findHandle==null)
    抛出新的InvalidOperationException(“必须首先调用MoveNext());
    返回新的FoundFileData(参考win32FindData);
    }
    }
    对象IEnumerator.Current
    {
    获取{返回当前;}
    }
    公共图书馆
    {
    if(_findHandle==null)
    {
    _findHandle=新的安全文件句柄(FindFirstFile(_fileName,out _win32FindData),true);
    如果(_findHandle.IsInvalid)
    {
    int lastError=Marshal.GetLastWin32Error();
    如果(lastError==未找到错误文件)
    返回false;
    抛出新的Win32Exception(lastError);
    }
    }
    其他的
    {
    如果(!FindNextFile(_findHandle,out _win32FindData))
    {
    int lastError=Marshal.GetLastWin32Error();
    if(lastError==错误\u无\u更多\u文件)
    返回false;
    抛出新的Win32Exception(lastError);
    }
    }
    返回true;
    }
    公共无效重置()
    {
    如果(_findHandle.IsInvalid)
    返回;
    _findHandle.Close();
    _findHandle.setHandlesInvalid();
    }
    公共空间处置()
    {
    _findHandle.Dispose();
    }
    #端区
    }
    公共类文件查找器:IEnumerable
    {
    只读字符串_文件名;
    公共文件查找器(字符串文件名)
    {
    _fileName=文件名;
    }
    公共IEnumerator GetEnumerator()
    {
    返回新的FileEnumerator(_fileName);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
    返回GetEnumerator();
    }
    }
    公共类FoundFileData
    {
    公共字符串替换文件名;
    公共文件属性;
    公共日期时间CreationTim
    
       public static IEnumerable<string> GetFilesUnmanaged(string directory, string filter)
            {
                return new FilesFinder(Path.Combine(directory, filter))
                    .Where(f => (f.Attributes & FileAttributes.Normal) == FileAttributes.Normal
                        || (f.Attributes & FileAttributes.Archive) == FileAttributes.Archive)
                    .Select(s => s.FileName);
            }
        }
    
    
    public class FilesEnumerator : IEnumerator<FoundFileData>
    {
        #region Interop imports
    
        private const int ERROR_FILE_NOT_FOUND = 2;
        private const int ERROR_NO_MORE_FILES = 18;
    
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
    
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool FindNextFile(SafeHandle hFindFile, out WIN32_FIND_DATA lpFindFileData);
    
        #endregion
    
        #region Data Members
    
        private readonly string _fileName;
        private SafeHandle _findHandle;
        private WIN32_FIND_DATA _win32FindData;
    
        #endregion
    
        public FilesEnumerator(string fileName)
        {
            _fileName = fileName;
            _findHandle = null;
            _win32FindData = new WIN32_FIND_DATA();
        }
    
        #region IEnumerator<FoundFileData> Members
    
        public FoundFileData Current
        {
            get
            {
                if (_findHandle == null)
                    throw new InvalidOperationException("MoveNext() must be called first");
    
                return new FoundFileData(ref _win32FindData);
            }
        }
    
        object IEnumerator.Current
        {
            get { return Current; }
        }
    
        public bool MoveNext()
        {
            if (_findHandle == null)
            {
                _findHandle = new SafeFileHandle(FindFirstFile(_fileName, out _win32FindData), true);
                if (_findHandle.IsInvalid)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    if (lastError == ERROR_FILE_NOT_FOUND)
                        return false;
    
                    throw new Win32Exception(lastError);
                }
            }
            else
            {
                if (!FindNextFile(_findHandle, out _win32FindData))
                {
                    int lastError = Marshal.GetLastWin32Error();
                    if (lastError == ERROR_NO_MORE_FILES)
                        return false;
    
                    throw new Win32Exception(lastError);
                }
            }
    
            return true;
        }
    
        public void Reset()
        {
            if (_findHandle.IsInvalid)
                return;
    
            _findHandle.Close();
            _findHandle.SetHandleAsInvalid();
        }
    
        public void Dispose()
        {
            _findHandle.Dispose();
        }
    
        #endregion
    }
    
    public class FilesFinder : IEnumerable<FoundFileData>
    {
        readonly string _fileName;
        public FilesFinder(string fileName)
        {
            _fileName = fileName;
        }
    
        public IEnumerator<FoundFileData> GetEnumerator()
        {
            return new FilesEnumerator(_fileName);
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    
    public class FoundFileData
    {
        public string AlternateFileName;
        public FileAttributes Attributes;
        public DateTime CreationTime;
        public string FileName;
        public DateTime LastAccessTime;
        public DateTime LastWriteTime;
        public UInt64 Size;
    
        internal FoundFileData(ref WIN32_FIND_DATA win32FindData)
        {
            Attributes = (FileAttributes)win32FindData.dwFileAttributes;
            CreationTime = DateTime.FromFileTime((long)
                    (((UInt64)win32FindData.ftCreationTime.dwHighDateTime << 32) +
                     (UInt64)win32FindData.ftCreationTime.dwLowDateTime));
    
            LastAccessTime = DateTime.FromFileTime((long)
                    (((UInt64)win32FindData.ftLastAccessTime.dwHighDateTime << 32) +
                     (UInt64)win32FindData.ftLastAccessTime.dwLowDateTime));
    
            LastWriteTime = DateTime.FromFileTime((long)
                    (((UInt64)win32FindData.ftLastWriteTime.dwHighDateTime << 32) +
                     (UInt64)win32FindData.ftLastWriteTime.dwLowDateTime));
    
            Size = ((UInt64)win32FindData.nFileSizeHigh << 32) + win32FindData.nFileSizeLow;
            FileName = win32FindData.cFileName;
            AlternateFileName = win32FindData.cAlternateFileName;
        }
    }
    
    /// <summary>
    /// Safely wraps handles that need to be closed via FindClose() WIN32 method (obtained by FindFirstFile())
    /// </summary>
    public class SafeFindFileHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool FindClose(SafeHandle hFindFile);
    
        public SafeFindFileHandle(bool ownsHandle)
            : base(ownsHandle)
        {
        }
    
        protected override bool ReleaseHandle()
        {
            return FindClose(this);
        }
    }
    
    // The CharSet must match the CharSet of the corresponding PInvoke signature
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct WIN32_FIND_DATA
    {
        public uint dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }
    
    public UserVolumeGenerator()
        {
            SetNumVolumes((short)100);
            SetNumSubVolumes((short)1000);
            SetVolumesRoot("/var/myproj/volumes");
        }
    
        public String GenerateVolume()
        {
            int volume = random.nextInt(GetNumVolumes());
            int subVolume = random.nextInt(GetNumSubVolumes());
    
            return Integer.toString(volume) + "/" + Integer.toString(subVolume);
        }
    
        private static final Random random = new Random(System.currentTimeMillis());
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern IntPtr FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData);
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool FindNextFile(IntPtr hDindFile, ref WIN32_FIND_DATA lpFindFileData);
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MashalAs(UnmanagedType.Bool]
    static extern bool DeleteFile(string lpFileName)
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MashalAs(UnmanagedType.Bool]
    static extern bool DeleteDirectory(string lpPathName)
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool FindClose(IntPtr hFindFile);
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLAstError = true)]
    static extern uint GetFileAttributes(string lpFileName);
    
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLAstError = true)]
    static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);
    
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), Serializable, BestFitMapping(false)]
        internal struct WIN32_FIND_DATA
        {
            internal FileAttributes dwFileAttributes;
            internal FILETIME ftCreationTime;
            internal FILETIME ftLastAccessTime;
            internal FILETIME ftLastWriteTime;
            internal int nFileSizeHigh;
            internal int nFileSizeLow;
            internal int dwReserved0;
            internal int dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            internal string cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            internal string cAlternative;
        }
    
    public void RemoveDirectory(string directoryPath)
    {
        var path = @"\\?\UNC\" + directoryPath.Trim(@" \/".ToCharArray());
        SearchAndDelete(path);
    }
    
    private void SearchAndDelete(string path)
    {
        var fd = new WIN32_FIND_DATA();
        var found = false;
        var handle = IntPtr.Zero;
        var invalidHandle = new IntPtr(-1);
        var fileAttributeDir = 0x00000010;
        var filesToRemove = new List<string>();
        try
        {
            handle = FindFirsFile(path + @"\*", ref fd);
            if (handle == invalidHandle) return;
            do
            {
                var current = fd.cFileName;
                if (((int)fd.dwFileAttributes & fileAttributeDir) != 0)
                {
                    if (current != "." && current != "..")
                    {
                        var newPath = Path.Combine(path, current);
                        SearchAndDelete(newPath);
                    }
                }
                else
                {
                    filesToRemove.Add(Path.Combine(path, current));
                }
                found = FindNextFile(handle, ref fd);
            } while (found);
        }
        finally
        {
            FindClose(handle);
        }
        try
        {
            object lockSource = new Object();
            var exceptions = new List<Exception>();
            Parallel.ForEach(filesToRemove, file, =>
            {
                var attrs = GetFileAttributes(file);
                attrs &= ~(uint)0x00000002; // hidden
                attrs &= ~(uint)0x00000001; // read-only
                SetFileAttributes(file, attrs);
                if (!DeleteFile(file))
                {
                    var msg = string.Format("Cannot remove file {0}.{1}{2}", file.Replace(@"\\?\UNC", @"\"), Environment.NewLine, new Win32Exception(Marshal.GetLastWin32Error()).Message);
                    lock(lockSource)
                    {
                        exceptions.Add(new Exceptions(msg));
                    }
                }
            });
            if (exceptions.Any())
            {
                throw new AggregateException(exceptions);
            }
        }
        var dirAttr = GetFileAttributes(path);
        dirAttr &= ~(uint)0x00000002; // hidden
        dirAttr &= ~(uint)0x00000001; // read-only
        SetfileAttributtes(path, dirAttr);
        if (!RemoveDirectory(path))
        {
            throw new Exception(new Win32Exception(Marshal.GetLAstWin32Error()));
        }
    }
    
    private void DeleteDirectoryTree(List<string> directories)
    {
            // group directories by depth level and order it by level descending
            var data = directories.GroupBy(d => d.Split('\\'),
                d => d,
                (key, dirs) => new
                {
                    Level = key,
                    Directories = dirs.ToList()
                }).OrderByDescending(l => l.Level);
            var exceptions = new List<Exception>();
            var lockSource = new Object();
            foreach (var level in data)
            {
                Parallel.ForEach(level.Directories, dir =>
                {
                    var attrs = GetFileAttributes(dir);
                    attrs &= ~(uint)0x00000002; // hidden
                    attrs &= ~(uint)0x00000001; // read-only
                    SetFileAttributes(dir, attrs);
                    if (!RemoveDirectory(dir))
                    {
                        var msg = string.Format("Cannot remove directory {0}.{1}{2}", dir.Replace(@"\\?\UNC\", string.Empty), Environment.NewLine, new Win32Exception(Marshal.GetLastWin32Error()).Message);
                        lock (lockSource)
                        {
                            exceptions.Add(new Exception(msg));
                        }
                    }
                });
            }
            if (exceptions.Any())
            {
                throw new AggregateException(exceptions);
            }
    }