【问题标题】:C# WPF Speedup (Thread) Total FileInfo.Length from Multiple FilesC# WPF 加速(线程)来自多个文件的总 FileInfo.Length
【发布时间】:2026-01-25 04:50:01
【问题描述】:

我正在尝试加速由一个路径递归给出的所有文件夹中所有文件的总和计算。

假设我选择“E:\”作为文件夹。 我现在将以毫秒为单位通过“SafeFileEnumerator”将条目递归文件列表放入 IEnumerable(就像一个魅力)

现在我想从这个 Enumerable 中的所有文件中收集所有字节的总和。 现在我通过 foreach 循环它们并获取 FileInfo(oFileInfo.FullName).Length; - 对于每个文件。

这是有效的,但速度很慢 - 大约需要 30 秒。如果我通过 Windows 右键单击​​查找空间消耗 - Windows 资源管理器中所有选定文件夹的属性,我会在大约 6 秒内得到它们(在 ssd 上的 26 GB 数据中大约有 1600 个文件)

所以我的第一个想法是通过使用线程来加速收集,但我在这里没有得到任何加速..

没有线程的代码如下:

public static long fetchFolderSize(string Folder, CancellationTokenSource oCancelToken)
{
    long FolderSize = 0;

    IEnumerable<FileSystemInfo> aFiles = new SafeFileEnumerator(Folder, "*", SearchOption.AllDirectories);
    foreach (FileSystemInfo oFileInfo in aFiles)
    {
        // check if we will cancel now
        if (oCancelToken.Token.IsCancellationRequested)
        {
            throw new OperationCanceledException();
        }

        try
        {
            FolderSize += new FileInfo(oFileInfo.FullName).Length;
        }
        catch (Exception oException)
        {
            Debug.WriteLine(oException.Message);
        }
    }

    return FolderSize;
}

多线程代码如下:

public static long fetchFolderSize(string Folder, CancellationTokenSource oCancelToken)
{
    long FolderSize = 0;

    int iCountTasks = 0;

    IEnumerable<FileSystemInfo> aFiles = new SafeFileEnumerator(Folder, "*", SearchOption.AllDirectories);
    foreach (FileSystemInfo oFileInfo in aFiles)
    {
        // check if we will cancel now
        if (oCancelToken.Token.IsCancellationRequested)
        {
            throw new OperationCanceledException();
        }

        if (iCountTasks < 10)
        {
            iCountTasks++;
            Thread oThread = new Thread(delegate()
            {
                try
                {                            
                    FolderSize += new FileInfo(oFileInfo.FullName).Length;
                }
                catch (Exception oException)
                {
                    Debug.WriteLine(oException.Message);
                }

                iCountTasks--;
            });
            oThread.Start();
            continue;
        }

        try
        {
            FolderSize += new FileInfo(oFileInfo.FullName).Length;
        }
        catch (Exception oException)
        {
            Debug.WriteLine(oException.Message);
        }
    }

    return FolderSize;
}

有人可以给我一个建议,我可以如何加快文件夹大小的计算过程吗?

问候

编辑 1(Parallel.Foreach 建议 - 请参阅 cmets)

public static long fetchFolderSize(string Folder, CancellationTokenSource oCancelToken)
{
    long FolderSize = 0;

    ParallelOptions oParallelOptions = new ParallelOptions();
    oParallelOptions.CancellationToken = oCancelToken.Token;
    oParallelOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

    IEnumerable<FileSystemInfo> aFiles = new SafeFileEnumerator(Folder, "*", SearchOption.AllDirectories).ToArray();

    Parallel.ForEach(aFiles, oParallelOptions, oFileInfo =>
    {
        try
        {
            FolderSize += new FileInfo(oFileInfo.FullName).Length;
        }
        catch (Exception oException)
        {
            Debug.WriteLine(oException.Message);
        }
    });

    return FolderSize;
}

【问题讨论】:

    标签: c# wpf multithreading io filesize


    【解决方案1】:

    关于 SafeFileEnumerator 性能的旁注:

    一旦获得 IEnumerable,并不意味着您获得了整个集合,因为它是惰性代理。试试下面这个 sn-p - 我相信你会看到性能差异(对不起,如果它没有编译 - 只是为了说明这个想法):

    var tmp = new SafeFileEnumerator(Folder, "*", SearchOption.AllDirectories).ToArray(); // fetch all records explicitly to populate the array
    IEnumerable<FileSystemInfo> aFiles = tmp;
    

    现在你想要达到的实际结果。

    1. 如果您只需要文件大小 - 最好请求有关文件系统的操作系统功能,而不是逐个查询文件。我将从 DirectoryInfo 类开始(例如参见 http://www.tutorialspoint.com/csharp/csharp_windows_file_system.htm)。
    2. 如果您需要计算每个文件的校验和,这绝对是一项缓慢的任务,因为您必须先加载每个文件(大量内存传输)。线程在这里不是助推器,因为它们会受到操作系统文件系统吞吐量的限制,而不是您的 CPU 能力。

    【讨论】:

    • 感谢您快速回答,但如果我尝试使用您的片段,我看不出速度有任何变化。文件列表以毫秒为单位收集。顺便说一句:我可以在 Windows 的性能管理器中看到硬盘的性能 - 当我的代码正在运行并收集文件/文件夹的总和时,我看不到任何“强”硬盘/固态硬盘访问:-/
    【解决方案2】:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.IO;
    
    namespace ConsoleApplication3
    {
        class Program
        {
            static void Main(string[] args)
            {
                long size = fetchFolderSize(@"C:\Test", new CancellationTokenSource());
    
            }
    
                public static long fetchFolderSize(string Folder, CancellationTokenSource  oCancelToken)
        {
    
    
                ParallelOptions po = new ParallelOptions();
                po.CancellationToken = oCancelToken.Token;
                po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
    
                long folderSize = 0;
                string[] files = Directory.GetFiles(Folder);
    
                Parallel.ForEach<string,long>(files,
                                                po,
                                                () => 0,
                                                (fileName, loop, fileSize) => 
                                                {
                                                    fileSize = new FileInfo(fileName).Length;
                                                    po.CancellationToken.ThrowIfCancellationRequested();
                                                    return fileSize;
    
                                                },  
                                                (finalResult) => Interlocked.Add(ref folderSize, finalResult)
                                                );
    
    
                string[] subdirEntries = Directory.GetDirectories(Folder);
    
                Parallel.For<long>(0, subdirEntries.Length, () => 0, (i, loop, subtotal) =>
                {
                    if ((File.GetAttributes(subdirEntries[i]) & FileAttributes.ReparsePoint) !=
                          FileAttributes.ReparsePoint)
                        {
                            subtotal += fetchFolderSize(subdirEntries[i], oCancelToken);
                            return subtotal;
                        }
                        return 0;
                    },
                        (finalResult) => Interlocked.Add(ref folderSize, finalResult)
                    );
    
                return folderSize ;
        }
        }
    
    }
    

    【讨论】:

    • 递归并行任务
    • 在调试模式之外执行速度测试我得到的结果相当于在大型目录上的 Windows 资源管理器。
    • 感谢您的贡献。我刚刚测试了你的代码,但我有严重的问题:(如果我选择“E:/”作为起始文件夹来获取大小,我会从“Directory.GetDirectories(Folder)”中得到一个异常,因为它会(内部)尝试获取我无法访问的“系统卷信息”文件夹的文件夹信息。这就是为什么我切换到“SafeFileEnumerator”的原因。另一方面,我的测试总大小只有 64MB C:\users 文件夹,其中包含 33GB 数据:-/
    • 我没有调试你的代码,因为 Directory.GetDirectories(Folder);我不能使用(异常/系统目录)但我尝试将我的代码与您的 Parallel.Foreach 循环建议结合起来,该建议有效,但没有得到任何加速更改:-/