并行枚举目录会消耗很高的物理内存使用量

时间:2018-08-31 05:52:33

标签: c# io task-parallel-library ienumerable

我写了一个实用程序,可以在系统中的所有固定驱动器中搜索某些扩展名的文件。一些驱动器包含数百万个文件夹(例如,3000万个),并且可以在不同深度找到文件(例如,第6/7个子文件夹)。在我正在使用的功能下面找到

private void ReadDirectories(string targetDirectory)
    {
        IEnumerable<string> files = Directory.EnumerateFiles(targetDirectory).AsParallel();
        ConcurrentBag<string> filesBag = new ConcurrentBag<string>(files);
        Parallel.ForEach(filesBag, (file) =>
       {
           Interlocked.Increment(ref totalFileCount);
           if (extension is a text/excel/word file )
           {
               try
               {
                   // Some logic here
               }
               catch (AggregateException Aex)
               {
                   Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
               }
               catch (Exception ex)
               {
                   Log("File read failed: " + file + ex.Message + ex.StackTrace + ex.InnerException);
                   return; // This is break equivalent in Parallel.ForEach

               }
           }

       });

        IEnumerable<string> directories = Directory.EnumerateDirectories(targetDirectory).AsParallel();
        ConcurrentBag<string> directoryBag = new ConcurrentBag<string>(directories);
        Parallel.ForEach(directoryBag, (subDirectory) =>
         {
             try
             {
                 ReadDirectories(subDirectory);
             }
             catch (AggregateException Aex)
             {
                 Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
             }
             catch (UnauthorizedAccessException Uaex)
             {
                 Log("Unauthorized exception: " + Uaex.Message + Uaex.StackTrace + Uaex.InnerException);
                 return;
             }
             catch (AccessViolationException Aex)
             {
                 Log("Access violation exception: " + Aex.Message + Aex.StackTrace + Aex.InnerException);
                 return;
             }
             catch (Exception ex)
             {
                 Log("Error while reading directories and files : " + ex.Message + ex.StackTrace + ex.InnerException);
                 return;
             }
         });

    }

我面临的问题是,一旦应用程序开始枚举文件夹,物理内存就会越来越消耗,并在一段时间后达到峰值(99%)。此时,无法执行其他任何活动。但是在整个运行过程中,我的应用程序内存约为80 -90 MB。想知道物理内存使用率如此之高的原因,代码是否有问题?

2 个答案:

答案 0 :(得分:1)

考虑一下您的数量:3000万个文件夹,每个文件夹可能包含几个文件,这给您留下了大约1亿个文件和目录名称的字符串。并且由于该方法是递归的,因此所有袋子都保留到递归结束为止。

因此,文件/目录的平均名称长度为100个字符,仅这些名称最多可使用10GB的RAM。

答案 1 :(得分:0)

正如其他人所解释的那样,存储这么多字符串将耗尽很多内存,并且无法扩展。尝试枚举 parallel 中的文件夹和文件也不会加快处理速度。

使用Directory.EnumerateFiles甚至更好,将DirectoryInfo.EnumerateFilesSearchOption.AllDirectories一起使用来枚举当前文件夹和子文件夹中的所有文件并处理文件

一种快速而肮脏的选择是使用LINQ查询过滤所有目标文件,并使用Parallel.ForEach处理文件,例如:

var extensions=new[]{".docx", ".xlsx",...};
var folder=new DirectoryInfo(targetDirectory);
var files=from file in folder.EnumerateFiles("*.*", SearchOption.AllDirectories)
          where extensions.Contains(file.Extension,StringComparer.InvariantCultureIgnoreCase)
          select file;

Parallel.ForEach(files,file=>ProcessFile(file));

这将使用与计算机核心差不多数量的任务来处理文件。您可以通过指定其他MaxDegreeOfParallelism选项来使用更多任务:

var options=new ParallelOptions { MaxDegreeOfParallelism = 4 }
Parallel.ForEach(files,options,ProcessFile);

Parallel.ForEach将根据需要从files查询中提取文件名。 EnumerateFiles返回第一个结果后,它将立即开始处理,而不是等待所有文件名都加载并缓存在内存中。